Toast
Briefly confirms an action, then goes away.
Examples
Kind
defaultdefault
success
warn
fail
loading
Usage
When to use it
- Use
- The result of what the person just did is not visible where they are: copied, published.
- Use
- A background event finished or changed: an index rebuilt, an inbox sync fell behind.
- Use
- An action can be undone for a short time: archive with Undo.
- Not for
- The message must stay until its cause is fixed. Use Alert.
- Not for
- A form field is wrong. Use Field.
- Not for
- The result is already visible in the view. Use Progress.
- Not for
- The person must decide before continuing. Use Dialog.
Rules
11 rules
- Must
- Report a failure with toast.fail, never toast() with fail words. Because a plain toast() times out and waits behind the limit, while toast.fail stays until closed and always shows.
- Should
- Name what failed and why in a fail toast, and offer the retry as the action.
- Must
- Leave timeout unset so the reading time applies; pass timeout: 0 only to hold a toast. Because a fixed 3000ms takes the toast away before the person reads it and decides on Undo.
- Must
- Mount one Toaster at the app root and call toast() from anywhere. Because every Toaster reads the same manager, so two mounted Toasters show each toast twice.
- Must
- Use toast.promise, or reuse the id with toast.update, when work finishes later. Because toast.loading never times out, so a separate success toast leaves "Rebuilding" on screen beside "Rebuilt".
- Should
- Write a success title in the past tense with its object under 48 characters: 'Published refund-prompt v14'.
- Must
- Make a toast's action reachable in the view too, such as Undo in the row menu. Because the toast leaves on its timer, and a keyboard or screen reader user may never reach its button.
- Never
- Toast after every save; toast only when the result is not visible where the person is. Because a toast for a change already on screen teaches people to ignore the stack, including the fail that matters.
- Never
- Put an emoji or an exclamation mark in a title. Because a screen reader reads "party popper" before the title, and the title already says what happened.
- Should
- Keep the Toaster limit at 3 and let the rest wait.
- Should
- Let a loading toast show its own ring; never add a spinner to its title. Because the toast already leads with a running ring, and a second spinner pushes the title off its edge.
Accessibility
7 checks
- The viewport is a region labelled Notifications with aria-live="polite"; F6 moves focus into the stack and pauses timers.
- A fail toast has priority high and is repeated in a role="alert" node.
- Each toast is a dialog named by its title and described by its description.
- Tab moves through action and close; Escape closes the focused toast.
- The close button is named Dismiss; the drain line is aria-hidden decoration beside the words.
- Hover, focus in the stack and window blur pause the timer and the drain line.
- The stack clears the safe area inset; a swipe down or to the end dismisses.
Edge cases
9 edge cases
- Long title or 36-character ID
- Wraps and breaks; the toast grows and the reading time caps at 12 seconds.
- Ten toasts in a second
- Three show, the rest wait with timers stopped; a fail never waits.
- Empty title
- Adds nothing and returns an empty id.
- Right to left
- The stack sits at the inline end, a swipe toward that edge dismisses, and the drain line shrinks toward the start.
- Promise never settles
- The loading toast stays until closed.
- Reused id
- Replaces in place instead of stacking.
- Opened from a Dialog
- Portals into .quartz and sits above it.
- Forced colors
- Each toast gets a 1px outline; words carry the kind.
- Reduced motion
- No rise or fade; the drain line is hidden; the timer still runs.
API
10 props
toast(title, options)stringoptions.descriptionReactNodeoptions.action{label, onClick}options.timeoutnumberdefaultreading timeoptions.idstringdefaultgeneratedtoast.promise(p, {loading, success, fail})Promisetoast.update(id, kind, title, options)functiontoast.dismiss(id?)functionreadingTimeout(title, description?, hasAction?)numberToaster inline, limit, managerboolean, number, managerdefaultfalse, 3
Tokens
21 tokens
--surface-page- var(--paper)
--text-1- var(--ink)
--elev-overlay- 0 0 0 1px var(--line-frame), 0 12px 24px -12px oklch(var(--text-on-field-c) / var(--shadow-alpha))
--radius-frame- 10px
--toast-width- 384px
--gutter- 24px
--gutter-sm- 16px
--warn-text- oklch(49% 0.100 68)
--fail-text- oklch(51% 0.180 26)
--dur-spin- 800ms
--type-label- 400 14px/16px var(--font-sans)
--type-body-sm- 380 13px/18px var(--font-sans)
--text-2- oklch(var(--ink-c) / 0.80)
--text-3- oklch(var(--ink-c) / 0.66)
--dur-base- 160ms
--ease-step- cubic-bezier(0.2, 0, 0, 1)
--toast-read-base- 3000ms
--toast-read-char- 60ms
--toast-read-action- 3000ms
--toast-read-min- 4000ms
--toast-read-max- 12000ms