MiHCM
feedback/@mihcm/ui·v0.21.0·stable

Toast

Brief non-blocking notification that auto-dismisses. Imperative toast() API with semantic variants, appearance styles, and configurable duration.

Toast accessibility

Toast is built on sonner, which ships with WCAG-aware defaults. The MiHCM wrapper preserves them and adds token-based colour contrast.

Live region

  • The <Toaster /> mounts a single region (<section role="region" aria-label="Notifications">) so assistive tech announces new toasts without page nav.
  • Each row uses aria-live:
    • polite for toast(), toast.success(), toast.warning(), toast.info(), toast.message().
    • assertive for toast.error() — failures get announced immediately.
  • toast.loading() and toast.promise() rows update in place when re-keyed with the same id, so screen readers don't re-announce while a task progresses.

Keyboard

  • The Toaster region is focusable via the alt+T shortcut (sonner's default — configurable via the hotkey prop).
  • Once focused, Tab cycles into the toast row's action / cancel / close buttons.
  • Esc closes the focused row.
  • Toasts never steal focus from the user's current task — opening a toast doesn't move focus.

Timing

  • Default auto-dismiss is 5 s (configurable). Hovering or focusing a row pauses the timer, so users with slower reading speeds can finish before dismissal — satisfies WCAG 2.2.1 (Timing Adjustable).
  • For critical information that requires user action, use an Alert or AlertDialog instead — toasts auto-dismiss by design.
  • Pass duration: Infinity for a row that only dismisses on user action.

Colour is never the only signal

  • Type variants tint the surface (success / error / warning / info) but the title text always carries the primary meaning. Pair with descriptive copy ("Saved", "Network error") rather than a generic "Notification".
  • Per-type colours come from MiHCM tokens (--color-success, --color-destructive, --color-warning, --color-primary) so they meet the design system's contrast budget in both light and dark mode.

Touch + pointer

  • Each row supports swipe-to-dismiss (horizontal drag) on touch devices and pointer drag on desktop. The interaction starts after a small threshold, so vertical scrolls and stray touches won't dismiss accidentally.
  • The optional close button is sized 24 × 24 — comfortably above the WCAG 44 × 44 hit-target threshold when combined with the row's outer padding.

Motion

  • Entry/exit transitions respect prefers-reduced-motion: reduce (sonner reads the media query and shortens / disables animation). No additional configuration needed.

Screen-reader transcript example

Saving…
Saved Q4 report

A toast.promise() row produces those two consecutive announcements — the first when the loading state mounts, the second when the same id is replaced by the resolved value.

Audit checklist

  • One <Toaster /> mounted at the app root, not duplicated per page.
  • toast.error() used for failures (so the row is announced assertively).
  • Title is descriptive without leaning on the icon or colour.
  • Action button verbs are explicit ("Undo", "Retry") — not "OK" / "Click here".
  • No toast is the only indication that a destructive action succeeded — keep a status row in the UI as well.
  • Custom JSX toasts (toast.custom()) include semantic markup if they replace the entire row chrome.