Toast
A brief, non-blocking notification that confirms something happened — optionally with one action such as Undo — announced to assistive technology and gone on its own.
Basic usage
Install the package:
pnpm add @design-schema/react @design-schema/tokens Import the stylesheet once for the app, then the component:
import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { Toast } from '@design-schema/react'; Examples
import { Toast } from '@design-schema/react';
<Toast message="Message sent" />Props
6 rows| Prop | Type | Default | Description |
|---|---|---|---|
| message required | string | — | One sentence saying what happened ("Message sent", "3 files deleted"). |
| tone | neutral | success | warning | danger | neutral | Sets the leading icon; `neutral` has none. Toasts do not use tinted backgrounds — the icon and message carry the tone. |
| actionLabel | string | — | Label for a single action button ("Undo", "View"). When present the toast stays longer and pauses on hover and focus. |
| duration | short | long | persistent | short | `short` ≈ 5s, `long` ≈ 10s (both computed from motion.duration.loop × 6 / × 12 so themes without motion still get sensible times), `persistent` until dismissed. When `actionLabel` is set or `tone` is danger the toast is persistent regardless of this prop (a dev warning notes the override only when `duration` was passed explicitly as `short` or `long` — on Lit, when the property or attribute was assigned; the default never warns. It fires each time a change to `duration`, `actionLabel` or `tone` enters that case, including when the case already held and `duration` changes between `short` and `long`; examples and stories with an action or danger tone pass `persistent` or no `duration`, and a story's shared `meta` args never carry `duration` at all, since copying it onto every instance would mark them all as explicitly assigned). If motion.duration.loop resolves to 0 or cannot be resolved (no theme CSS, jsdom), both durations are treated as persistent. The two durations are computed at region mount from the resolved motion.duration.loop (getComputedStyle on the region on web/Lit), never hardcoded; a toast rendered outside a region, and every toast on native, computes them when it mounts — on native from the theme's token value, which is always a number, so only a resolved 0 reaches the persistent fallback. |
| dismissible | boolean | true | Shows a dismiss button. Persistent toasts are always dismissible. |
| toastId | string | — | Stable identity; showing a toast with the same toastId replaces the previous one instead of stacking (named toastId so it does not collide with the DOM `id` on Lit). It never becomes the DOM `id`; on a directly rendered Toast outside the region it is accepted and has no effect. |
Events
2 rows| Event | Type | Description |
|---|---|---|
| onAction | onAction / action | The action button was activated. The toast dismisses. |
| onDismiss | onDismiss / dismiss | The toast left the screen: reason `timeout`, `dismiss-button`, `escape`, `action`, `replaced` (a replaced or evicted toast leaves immediately, without its exit transition), or `programmatic` (which plays the exit transition like every reason but `replaced`). It fires when the toast begins to leave — synchronously, at the state change that starts the exit transition, while the toast is still connected so on Lit it still bubbles to the region — and the element is removed once the transition ends, the same split Popover uses. A caller that reacts to the event therefore never waits for `exit`, and the reason is known before the animation rather than after it. |
Accessibility
Decisions this component leaves to you
Supply an accessible name. This component has no text of its own to fall back on, so without one a screen reader announces its role and nothing else.
Implicit ARIA role: status.
Anatomy
What generation guarantees
Live region — Changes are announced through a live region, without moving focus.
Accessible name — Every instance carries a programmatic name. Where the component has no text of its own to use, the name is a prop the consumer supplies.
Escape dismisses — Escape closes it from anywhere inside.
Visible focus — Keyboard focus is always drawn, from the theme’s focus tokens, and is never removed.
Keyboard operable — Everything the pointer can do here, the keyboard can do — see the keyboard section.
Contrast — Text and meaningful non-text contrast meets WCAG AA; the pairs are listed below.
Reduced motion — Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.
Target size — Interactive targets are at least 24×24 CSS pixels (WCAG 2.2 Target Size, minimum).
No hover-only content — Nothing is reachable by hover alone; keyboard focus reveals the same content.
Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.