Toast
A headless, accessible toast system with an imperative global queue, polite live-region announcements, and focus-friendly auto-dismiss.
Features
- Global imperative queue
- No context plumbing
- Polite live region
- Paused auto-dismiss timers
- Focus restoration
- Composable parts
Usage
Mount one Toast.List near the root of your app or page. Calls to Toast.createToast add items to the queue, and the mounted list subscribes to that queue to render the toast items.
Keep Toast.List outside the button or form that creates the toast. The trigger only calls Toast.createToast; the list owns placement, stacking, visibility limits, and enter or exit state.
Installation
npm install @ariaui/toast
Examples
Four playgrounds ported from the legacy doc: default confirmation, success with an icon, destructive error styling with a retry-style control, and a neutral toast with an inline action. Each uses createToast, a mounted Toast.List in @ariaui/portal, and triggerRef for focus wiring.
Fenced code matches the previews via shared class strings in toastExampleTokens.ts; icons use @heroicons/react/24/solid.
List
Mount the list once, then push items into it with Toast.createToast.
No preview output yet.
Anatomy
import * as Toast from "@ariaui/toast";
import * as Portal from "@ariaui/portal";
export default function Example() {
return (
<Portal.Root>
<Toast.List>
<Toast.Item>
<Toast.Close />
</Toast.Item>
</Toast.List>
</Portal.Root>
);
}
API Reference
createToast
Imperative function that pushes a new toast into the global store and returns a dismiss handler for that toast. Any mounted `Toast.List` will render it immediately. `Toast.Item` is passed as `template` — you do not render it directly.
| Prop | Type | Default |
|---|---|---|
id* | string | — |
template* | React.ReactElement | — |
duration | number | — |
List
Container that subscribes to the global store and renders active toasts. Hovering the list pauses every item timer. Unmounting the list clears the queue. Non-stacked lists trim overflow immediately; stacked lists keep the current overflow item mounted through the exit animation.
| Prop | Type | Default |
|---|---|---|
triggerRef | React.RefObject<HTMLElement> | — |
stack | boolean | false |
stackOffset | number | 14 |
stackScaleStep | number | 0.08 |
visibleToasts | number | 3 |
| Attribute | Values |
|---|---|
| role | 'region' |
| aria-live | 'polite' |
| aria-label | 'Notifications' |
| data-stack | 'true' when `stack` is enabled |
| data-expanded | 'true' while hovered or focus-within; otherwise 'false' |
Item
Individual toast item. Manages the auto-dismiss timer and live-region semantics. Item hover, list hover, or focus pauses the timer; leave or blur restarts the full timer. Mounted and removed states drive the enter and exit animations.
| Prop | Type | Default |
|---|---|---|
duration | number | 3000 |
onClose | () => void | — |
| Attribute | Values |
|---|---|
| role | 'status' |
| aria-live | 'off' — the List container owns the live region |
| aria-atomic | 'true' — the toast is announced as a unit |
| data-state | 'open' |
| data-index | Zero-based newest-first item index when rendered by a List |
| data-stack | 'true' when rendered inside a stacked List |
| data-mounted | 'true' after the item has mounted and is ready to animate in |
| data-expanded | 'true' when the owning List is expanded |
| data-removed | 'true' while the bottom stacked toast is exiting before removal; otherwise 'false' |
| data-front | 'true' for the newest toast |
| data-visible | 'true' when the toast is inside the collapsed visible count, the extra over-limit layer, or the expanded stack |
| data-exiting | 'true' while the bottom stacked toast is scaling or fading out |
| data-exit-phase | 'scale' during the first overflow exit phase, then 'fade' before removal |
| --toast-index | Zero-based newest-first stack index |
| --toast-count | Total active toast count |
| --toast-offset | Collapsed stack Y offset; the newest toast resolves to 0px |
| --toast-offset-up | Collapsed upward stack offset derived from the owning List stackOffset |
| --toast-offset-down | Collapsed downward stack offset derived from the owning List stackOffset |
| --toast-scale | Collapsed stack scale; the newest toast resolves to 1 and older visible toasts shrink |
| --toast-exit-scale | Scale target used while a stacked overflow toast exits before fading out |
| --toast-visible-toasts | Resolved collapsed visible count |
| --toast-z-index | Newest-first stacking value for keeping the latest toast visually above older toasts |
Close
Button that dismisses its owning Item. Must be rendered inside `Toast.Item`. Calling `event.preventDefault()` in a custom `onClick` suppresses the built-in dismiss.
| Prop | Type | Default |
|---|---|---|
type | string | 'button' |
Keyboard
| Shortcut | Action |
|---|---|
| Tab | On a trigger wired to `triggerRef`, Tab moves focus into the Toast.List instead of the next focusable element. |
| Enter | On a focused Close button, dismisses the owning toast. |
| Space | On a focused Close button, dismisses the owning toast. |
Accessibility
Toast follows the WAI-ARIA status / live-region guidance for non-interruptive notifications:
Toast.Listis the live region —role="region",aria-live="polite", andaria-label="Notifications". Mount it once at the app root (typically inside a portal) so it's always in the accessibility tree.- Each
Toast.Itemisrole="status"witharia-live="off"andaria-atomic="true"so the content is announced as a single unit via the surrounding list, with no duplicate announcements. - Auto-dismiss timers pause when the list is hovered, when an item is hovered, and when an item receives focus — users relying on assistive tech or reading slowly are never cut off mid-content.
- Wire
triggerRefonToast.Listto the opening button so Tab from the trigger jumps into the toast stack and focus returns there once the queue empties. - Keep toast content short and action-focused. If a toast contains a dismiss-only action, give the
Closebutton anaria-label(e.g. "Dismiss notification"); avoid icon-only close buttons without an accessible name. - Do not use
aria-live="assertive"for routine confirmations — reserve assertive announcements for errors via a dedicated alert, not the toast region.