Toast
elements man ui/templates/toastShort notices that stack at an edge of the screen, pause on hover and close on their own or with a swipe.
Exampleimport { Toaster, Toast, toast } from "@elements/ui/toast"; import { Button } from "@elements/ui/button"; <!-- once, in the app shell --> <Toaster richColors /> <!-- anywhere in browser code --> <Button onclick={() => toast.success("Event created", { description: "Sunday, December 3 at 9:00 AM", action: { label: "Undo", onClick: () => undo() }, })}>Create event</Button> <Button onclick={() => toast.promise(save(form), { loading: "Saving...", success: (doc) => `Saved ${doc.name}`, error: "Could not save", })}>Save</Button> <!-- declarative: open is two-way --> <Toast open={state.saved} title="Saved" type="success" /> <!-- anchored: next to the button instead of in the stack --> <Button onclick={(e) => toast("Copied", { anchor: e.currentTarget as Element, positioner: { side: "top", sideOffset: 8, arrow: true }, })}>Copy</Button>
Parts
| Part | What it is |
|---|---|
Toaster |
The region toasts render in: a top-layer <section> with one stack per position. Mount it once, in the app shell. |
Toast |
A declarative toast: shown while open is true; writes open = false back when it closes itself (timer, swipe, close, Escape, action). A literal open is uncontrolled: it shows when it renders. |
toast() |
The imperative API, imported beside Toaster: toast(title, options), toast.success, info, warning, error, loading, message, promise, custom, update, dismiss, getToasts (getActiveToasts) and getHistory. Same id updates a toast in place. |
.toast |
One toast: .toast-close, .toast-icon (loader and icon in one cell), .toast-arrow (anchored, with positioner.arrow), .toast-content (.toast-title, .toast-description), .toast-cancel, .toast-action. A toast.custom() toast holds .toast-custom instead. |
Attributes
Toaster
| Attribute | Type | Default | Description |
|---|---|---|---|
position |
"top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" |
"bottom-right" |
Where the stack sits. A toast may name its own position; it then renders in a second stack. |
expand |
boolean |
false |
Keep the stack fanned out. Otherwise it fans out on hover, focus or the hotkey. |
visibleToasts |
number |
3 |
Toasts past this many are hidden and inert until those in front close. |
duration |
number |
4000 |
Default time on screen in ms. A toast's own duration wins; Infinity stays until dismissed. |
gap |
number |
-1 |
px between fanned-out toasts. -1 reads the theme (--space-3-5). |
offset |
string | number | { top, right, bottom, left } |
"" |
Distance from the viewport edges. "" reads the theme (--space-6). |
mobileOffset |
string | number | { top, right, bottom, left } |
"" |
The same under 600px. "" reads the theme (--space-4). |
closeButton |
boolean |
false |
A close button on every toast. |
richColors |
boolean |
false |
Success, info, warning and error toasts take their status colors. |
invert |
boolean |
false |
Inverse colors: a dark toast on a light page. |
theme |
"" | "light" | "dark" | "system" |
"" |
Light or dark toasts whatever the page's mode; system follows the OS. "" follows the page. |
hotkey |
string[] |
["altKey", "KeyT"] |
Focuses the region and fans out the stack. Each entry is a modifier property or an event.code or key, such as ["F8"]. |
dir |
"ltr" | "rtl" | "auto" |
"auto" |
Text direction of the stacks; auto reads the document. |
swipeDirections |
SwipeDirection[] |
[] |
Directions a swipe may dismiss toward. [] derives them from the position (bottom-right: bottom and right). |
swipeThreshold |
number |
45 |
px a swipe must travel to dismiss on release. A quick flick also dismisses. |
id |
string |
"" |
Names this Toaster; only toasts with the same toasterId render in it. |
label |
string |
"Notifications" |
The region's name; the hotkey is appended. |
ariaLabel |
string |
"" |
Replaces the whole region name. |
closeButtonLabel |
string |
"Close toast" |
The close button's accessible name. |
icons |
{ success?, info?, warning?, error?, loading?, close?: IconName | false } |
{} |
Replace or hide the icon per type. |
toastOptions |
{ class?, classes?, style?, duration?, closeButton?, unstyled?, closeButtonLabel? } |
{} |
Defaults for every toast in this Toaster. |
class |
string |
"" |
Extra classes on the region. |
style |
string |
"" |
Inline style on the region. |
Toast
| Attribute | Type | Default | Description |
|---|---|---|---|
open |
boolean (two-way) |
false |
Shows the toast while true. Bind it; the toast writes false back when it closes itself. |
title, description, type |
string | ((t: ToastItem) => string), string | ((t: ToastItem) => string), ToastType |
"", "", "default" |
Its content and kind. |
duration, dismissible, closeButton, richColors, invert, important, icon, action, cancel, position, toasterId, swipeDirections, testId, class |
as the toast() options |
as the toast() options |
The same options as toast(). |
onOpenChange |
(open: boolean) => void |
() => {} |
Called when the toast closes itself; not when the caller sets open. |
onEscapeKeyDown, onPause, onResume, onSwipeStart, onSwipeMove, onSwipeEnd, onSwipeCancel |
functions |
() => {} |
As the toast() options. |
toast() options
| Attribute | Type | Default | Description |
|---|---|---|---|
description |
string | ((t: ToastItem) => string) |
"" |
Second line under the title. A function is given the toast and re-renders when what it reads changes. |
data |
unknown |
undefined |
Your own values, for a function title or description (t.data) and toast.custom(). |
anchor |
Element | null |
null |
Shows the toast next to this element instead of in the stack. |
positioner |
{ side?, align?, sideOffset?, alignOffset?, arrow?, arrowPadding?, collisionAvoidance?, collisionBoundary?, collisionPadding?, sticky?, positionMethod?, disableAnchorTracking?, class?, style? } |
{} |
How an anchored toast sits against its anchor; side defaults to top. arrow draws one pointing at the anchor. |
swipeDirections |
SwipeDirection[] |
the Toaster's |
The directions that dismiss this toast, over the Toaster's. |
id |
string | number |
generated |
Reuse an id to update a toast in place; its timer restarts. |
action |
{ label, onClick?, altText?, class?, style? } | Node |
none |
A primary button. It closes the toast unless onClick calls e.preventDefault(). altText is read to screen readers. An element of your own renders in its place and keeps its own handlers. |
cancel |
{ label, onClick?, class?, style? } | Node |
none |
A secondary button; runs onClick and closes. An element of your own renders in its place. |
duration |
number |
the Toaster's |
ms on screen; Infinity stays. |
dismissible |
boolean |
true |
false: no swipe, no close, no Escape. |
icon |
IconName | false |
the type's |
Replace the type icon, or hide it. |
important |
boolean |
false |
Announced assertively, interrupting the screen reader. |
closeButton, richColors, invert, position, toasterId |
as on Toaster |
the Toaster's |
Override the Toaster's for this toast. |
testId, class, classes, style, unstyled |
string, string, ToastClasses, string, boolean |
"", "", {}, "", false |
data-testid, extra classes (per part with classes), inline style, and no chrome. |
onDismiss, onAutoClose, onPause, onResume, onEscapeKeyDown, onSwipeStart, onSwipeMove, onSwipeEnd, onSwipeCancel |
functions |
none |
Closed by the user or toast.dismiss; closed by its timer; timer paused and resumed; Escape (preventable); swipe phases. |
onClose, onRemove |
(t: ToastItem) => void |
none |
The toast starts to close, for any reason; its exit finished and it left the page. |
Keyboard
| Keys | Action |
|---|---|
Alt + T |
The hotkey: focuses the region and fans out the stack, from anywhere on the page. Pressed again from inside, returns focus. |
Tab / Shift + Tab |
Moves through the toasts, newest first, and their buttons. Tab past the last returns focus to where the hotkey was pressed. |
Space / Enter |
On the action, cancel or close button: runs it and closes the toast. |
Escape |
On a toast: closes it and moves focus to the next. On the region: folds the stack and returns focus. |
Data attributes
| Attribute | Values | On | Description |
|---|---|---|---|
data-y-position, data-x-position |
"top" | "bottom"; "left" | "center" | "right" |
.toaster-list, .toast | |
data-expanded |
"true" | "false" |
.toaster-list, .toast | Fanned out. |
data-type |
"default" | "success" | "info" | "warning" | "error" | "loading" |
.toast | |
data-mounted |
"true" | "false" |
.toast | false for the first frame; the enter transition runs off it. |
data-removed |
"true" | "false" |
.toast | true during the exit. |
data-state |
"open" | "closed" |
.toast | closed during the exit. |
data-front |
"true" | "false" |
.toast | The newest toast. |
data-visible |
"true" | "false" |
.toast | false past visibleToasts. |
data-index |
0..n | .toast | |
data-swiping, data-swiped, data-swipe-out |
"true" | "false" |
.toast | |
data-swipe |
"start" | "move" | "cancel" | "end" |
.toast | The phase of a swipe. |
data-swipe-direction |
"left" | "right" | "up" | "down" |
.toast | Where a swiped toast leaves. |
data-rich-colors, data-invert, data-dismissible, data-promise, data-styled |
"true" | "false" |
.toast | |
--offset, --index, --z-index, --initial-height, --swipe-amount-x, --swipe-amount-y |
custom properties | .toast | Layout and swipe offsets for custom styles. |
--toast-index, --toast-height, --toast-offset-y, --toast-swipe-movement-x, --toast-swipe-movement-y |
custom properties | .toast | The same numbers under these names too; --toast-frontmost-height on the list. |
data-limited, data-starting-style, data-ending-style |
"true" | "false"; present |
.toast | Past visibleToasts; before the enter; during the exit. |
data-update-key, data-updated |
count; present | .toast | Updates by id; set again on each, so a CSS animation on it replays. |
data-anchored, data-side, data-align, data-anchor-hidden |
present; side; align | .toaster-list | An anchored toast's list, as placed against its anchor. |
data-behind |
"true" | "false" |
.toast-content | Behind the front toast. |
data-swipe-ignore |
present | your element | A press on it never starts a swipe; nor does one on a button, link or field. |
Accessibility
- The region is a
<section>named "Notifications alt+T": the hotkey is in its name so it can be found. - Each toast is announced when it appears and when its title or type changes (a promise settling): title, description and the action's altText. important toasts use the assertive live region; others the polite one.
- Each toast is role="status" with aria-live off: named by its title (aria-labelledby) and described by its description (aria-describedby), so a focused toast is read by name; its announcement comes from the live region.
- Toasts never take focus on their own. The hotkey moves focus into the region; Tab past the last toast, or Escape on the region, returns it.
- Timers pause while the pointer is over the stack, while keyboard focus is in it, while a toast is pressed (a press released off the stack lets it go), while the window is in the background and while the tab is hidden.
- Hidden toasts beyond visibleToasts are inert. The close button has an accessible name; icons are decorative. On a non-dismissible toast the close and cancel buttons are aria-disabled.
- With prefers-reduced-motion, toasts appear, restack and swipe out with no motion.
- The region is a manual popover in the top layer, re-shown when a toast arrives, so toasts draw above dialogs and popovers opened before them. While a native modal dialog is open the region moves into the dialog's panel (still in the top layer), so its toasts are announced from a live region inside the dialog, and the hotkey, Tab, presses, hover and swipes reach them while the dialog stays open. Escape on a toast closes the toast, not the dialog.
See also
- Button (opens in a new tab): A button or a link that looks like one, in six variants and four sizes.