Feedback
Toast
A transient confirmation, mounted once near the app root.
Usage
When to reach for it
import { Toaster } from '@misoto22/design'Notes
Transient confirmations, mounted once near the app root.
A toast is for something that succeeded and needs no response. Anything the reader must act on belongs in the page — a toast is dismissed by time, and time is not an acknowledgement.
theme follows data-mode. sonner defaults to light and hard-codes the description's colour per theme — #3f3f3f, overridden only under [data-sonner-theme=dark] — so a Toaster that never passed theme put dark grey text on this system's --paper: #0d0d0d and every toast(title, { description }) lost its second half. Pass theme yourself and that wins; sonner's own system is the one value not to reach for, since it reads the OS rather than the attribute the page is actually painted from.
richColors stays off by default: sonner's rich palette is not this system's status scale, and turning it on reintroduces chroma the White Reset spends only on state.
Anatomy
| Element | Description |
|---|---|
| Toasterrequired | The single mount, bottom-right by default. It renders sonner’s list into a portal at the end of body, which is why the theming is an inline style — a stylesheet scoped to the app never reaches there. |
| Token stylerequired | sonner’s --normal-bg, --normal-text, --normal-border and --border-radius pointed at --paper, --ink, --rule-2 and --radius, plus the sans face. The --success-* and --error-* pairs join them only when richColors is on, which is the only state sonner reads them in. |
| Notification regionrequired | sonner’s own section, aria-live="polite" and labelled “Notifications altKey+KeyT”. Taken out of the tab order and reachable by that shortcut, and polite for every toast type — there is no assertive path. |
| Toast | One entry, pushed by toast() or one of its typed variants and removed from the DOM after sonner’s default four seconds, which this wrapper does not change. Three are visible at once; the rest queue. |
| Close button | On by default here, where sonner ships it off. Without it the only ways out are the timer and a swipe, and a keyboard has no swipe. |
Best practices
Do
- Mount exactly one Toaster. Every toast() call reaches every Toaster listening, so one in a root layout and one in a nested layout render the same message twice, in two corners.
- Set duration per toast for anything longer than a short sentence. The wrapper leaves sonner’s default at four seconds — roughly ten words read aloud — and the rest of the message is removed from the page before it has been read.
- Keep the message to what happened. Three toasts are visible at a time and the rest wait their turn, so a loop that toasts per item shows the last three and delivers the others after the reader has moved on.
- Set data-mode on <html> and leave theme alone. sonner hard-codes the description’s colour per theme — #3f3f3f, overridden only under its dark theme — so a page painted dark by an attribute the Toaster was not following put that grey on --paper at roughly 1.85:1, and every toast with a description lost its second half.
Don’t
- Nothing a reader must act on belongs here. Four seconds is a deadline they were never told about, and the button is in a portal at the end of body that a keyboard reaches last — an Undo in a toast is an offer most people cannot take.
- Nothing a reader must read twice belongs here either — an error code, a reference, a name to type elsewhere. There is no history: once the timer expires the text is out of the DOM and unrecoverable.
- Do not report a failure with toast.error and consider it reported. The region is polite for every type, so the failure queues behind whatever the screen reader was already saying and can be removed before its turn comes.
- Do not pass theme="system". It reads prefers-color-scheme, not data-mode, so a reader who overrode a dark operating system with a light page gets a dark toast over it — the same defect the old default had, pointing the other way.
Examples
default
The three types, and the single Toaster they all reach. Mount exactly one, near the app root: every toast call reaches every Toaster listening, so a second one in a nested layout renders the same message twice, in two corners. The region is aria-live polite for every type — there is no assertive path here — so a failure reported with toast.error queues behind whatever the screen reader was already saying, and can be removed by the timer before its turn comes. Report failures in the page as well.
long enough to read
Four seconds is the default, and four seconds is roughly ten words read aloud: anything longer than a short sentence needs a duration of its own, or the rest of it is removed from the page before it has been read. Keep the message to what happened, too — three toasts are visible at once and the rest queue, so a loop that toasts per item shows the last three and delivers the others after the reader has moved on. There is no Toaster in this example on purpose: the one mounted by the first example on this page is the only one the page should have.
not in a toast
The same failure twice: once in the page, once on a timer. Nothing a reader must act on belongs in the one that vanishes — four seconds is a deadline they were never told about, and the close button lives in a portal at the end of body that a keyboard reaches last. Nothing they must read twice belongs there either: there is no history, so once the timer expires the reference is out of the DOM and unrecoverable. Press the button and watch the number go; the Alert above it is still there, and still says what to do.
Payment declined