toast()

toast.mjs · UiElement.mjs

Use a toast for a brief announcement (saved, sent, deleted). For the currently active top status that stays visible until it's resolved (offline, sync error, missing assignment) use <ui-statusbar> with the notifications store — the statusbar displays the highest-priority item inline with role="status" so screen readers hear it; <ui-notifications> lets users open a popover containing all notifications.

ExportSignatureDescription
toast toast(message, { type?, duration?, error?, action_label?, action_href?, action_callback? }) Shows a brief notification at the bottom of the screen; returns a function that dismisses it
toast_timing { info_ms, error_ms, action_ms } Configurable default durations; tests can set a duration to zero to avoid waiting
get_history get_history() Returns an array of previous toasts (newest first)
clear_history clear_history() Empties the history

type: "info" (default) | "error" | "warning" · duration: ms, default 4,000 (info) / 6,000 (error) / 10,000 (any toast with an action) · the countdown pauses while the pointer is on the toast or focus is inside it · duration: Infinity never auto-dismisses — use the returned function · Event: document dispatches 'toast' with {message, type} in detail · CSS: --toast-align stacks the chips at one end of the bottom edge (Placement)

1. Info toast

Basic toast with default settings. Disappears after 4 seconds.

Show code

2. Error toast

Red background, role="alert" for assertive screen-reader announcement. Disappears after 6 seconds.

Show code

3. Custom duration

Control how long the toast is shown with duration (ms).

Show code

4. Stacked

Multiple toasts shown at once. Each has its own timer.

Show code

5. Mixed

Info and error toasts can be shown at the same time.

Show code

6. Typical usage

Examples of toasts in try/catch flows, as in production code.

Show code

7. Action

Add an action when the message suggests something the user can do. Pass action_label with action_callback for a button or action_href for a link. Supplying both creates a link that also runs the callback. The control sits inside the toast's <output> and is included in its announcement; see Accessibility.

An action toast stays visible for 10,000 ms rather than 4,000 or 6,000. The countdown pauses while the pointer is over a toast or focus is inside it, giving users time to reach the action.

The action inherits the toast's text color through currentColor. The default toast has a contrasting background, while error and warning use dark red and orange backgrounds in both themes. currentColor keeps the action and its focus ring legible on all three. Do not apply .btn styles to toast actions: they are designed for page surfaces and can lack contrast here. For example, an .outline button uses --text-1 for its label, matching the default toast's background.

For an action the user should still be able to reach after the toast has gone, raise it through the notifications store as well — it publishes the same three option names, so both can use the same options object.

Show code

8. History

All toasts are saved to a history (max 50). Listen to the document event 'toast' for real-time updates. An entry keeps action_label when one was supplied. The action handler is no longer available after dismissal, so only its label is recorded; message remains unchanged because callers compare it directly.

History (0)
TypeMessageActionTime
Show code

9. Placement

Chips stack in a lane across the bottom edge of the viewport, centred, with the newest at the bottom. A page sets --toast-align to stack them at one end of that lane instead — flex-end for the trailing corner, flex-start for the leading one. Each chip gets a full-width row inside the lane, and the property is that row's justify-content, so any justify-content value works and center is the default.

Set it wherever the container inherits from, which is anywhere above <body>:

Show code :root { --toast-align: flex-end; }

Use it when the page keeps a control on the bottom edge. A centred control is the case that needs this: the chips land on it, and because the container is a popover in the top layer, no z-index on the control can lift it clear. Move the stack to the end the control leaves free.

A chip wraps at a readable measure (--size-content-2, or the lane's width when that is narrower) rather than running the lane's whole width. The token is in ch, so the measure follows the chip's own type size. That cap is what makes the alignment worth setting: a chip only clears a control at the opposite end of the lane while it is narrower than the distance between them, and an uncapped one grows straight back across it — a plain error sentence drew a single line over 1000px long on a wide screen. Keep messages to a sentence or two; a long one still wraps to several lines and takes up that much more of the lane.

There is no property for the other axis, and that is deliberate. The enter animation wipes each chip upwards out of the lane's bottom edge: its wrapper grows from zero height with the chip pinned to the wrapper's rising top edge, so the wrapper's own overflow: hidden reveals it from the bottom up. The clip is local, so a lifted lane would still animate — but the wipe reads as motion because the edge it emerges from is the screen's. Away from that edge the same animation looks like a chip materialising out of an invisible seam, and giving it an edge again means a second clip supplied by the page. Keep the stack on the bottom edge and move it sideways.

Show code

Accessibility

The container is a popover="manual" element with role="status", aria-live="polite" and aria-atomic="true"; an error toast additionally carries role="alert" and aria-live="assertive" on its own <output>. Roles are set when the element is created and never flipped afterwards — screen readers handle a runtime role change inconsistently.