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.

Preview

No preview output yet.

live.tsxReady

Anatomy

tsx
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.

PropTypeDefault
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.

PropTypeDefault
triggerRef
React.RefObject<HTMLElement>
stack
booleanfalse
stackOffset
number14
stackScaleStep
number0.08
visibleToasts
number3
AttributeValues
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.

PropTypeDefault
duration
number3000
onClose
() => void
AttributeValues
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-indexZero-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-indexZero-based newest-first stack index
--toast-countTotal active toast count
--toast-offsetCollapsed stack Y offset; the newest toast resolves to 0px
--toast-offset-upCollapsed upward stack offset derived from the owning List stackOffset
--toast-offset-downCollapsed downward stack offset derived from the owning List stackOffset
--toast-scaleCollapsed stack scale; the newest toast resolves to 1 and older visible toasts shrink
--toast-exit-scaleScale target used while a stacked overflow toast exits before fading out
--toast-visible-toastsResolved collapsed visible count
--toast-z-indexNewest-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.

PropTypeDefault
type
string'button'

Keyboard

ShortcutAction
TabOn a trigger wired to `triggerRef`, Tab moves focus into the Toast.List instead of the next focusable element.
EnterOn a focused Close button, dismisses the owning toast.
SpaceOn a focused Close button, dismisses the owning toast.

Accessibility

Toast follows the WAI-ARIA status / live-region guidance for non-interruptive notifications:

  • Toast.List is the live region — role="region", aria-live="polite", and aria-label="Notifications". Mount it once at the app root (typically inside a portal) so it's always in the accessibility tree.
  • Each Toast.Item is role="status" with aria-live="off" and aria-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 triggerRef on Toast.List to 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 Close button an aria-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.
Previous
Textarea
Next
Toggle