Toast
A brief, self-dismissing confirmation of the reader's own action, built on Base UI Toast.
Preview
toast · base base · style base-lyraOpen under the product themeInstallation
pnpm dlx shadcn@latest add @opsinjs/toast
The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.
Usage
import { Toast } from "@/components/ui/toast"
import { Toast as ToastManager } from "@base-ui/react/toast"function SaveButton() {
const manager = ToastManager.useToastManager()
return (
<button onClick={() => manager.add({ title: "Saved" })}>Save note</button>
)
}
function Screen() {
return (
<Toast position="bottom-right">
<SaveButton />
</Toast>
)
}When to use it
Use it when
- Confirming an action the reader took, such as a note saved or a reminder set, where missing it costs nothing.
- Acknowledging a background action that completed, such as a sync finishing, which the reader loses nothing by missing.
- Offering a brief optional way to reverse the action, such as an Undo the reader may take or ignore.
Do not use it when
- The message is a reading, a result, or anything about the reader's health they must not miss.Use
alert-bannerinstead. - The message needs attention now and stays until the reader has dealt with it.Use
alert-bannerinstead. - The information is a standing explanation, note or caveat that belongs on the surface while they read.Use
calloutinstead.
Anatomy
ToastThe region. Renders Base UI's provider and a fixed viewport, and mounts children inside so they can push a toast.Controlled bypositionToast.ViewportThe live region the stack sits in: role="region", aria-live="polite", named by label, reachable with F6.Controlled bylabelToast.ItemOne toast, role="dialog" labelled by its title. Card material, hairline and overlay shadow, so it reads as lifted.Toast.TitleThe confirmation itself, such as "Saved". The full foreground, drawn only when the pushed toast carries one.Toast.DescriptionAn optional second line of detail. The muted role at the footnote step.Toast.ActionAn optional quiet text control such as Undo, drawn when the pushed toast carries actionProps.Toast.CloseThe dismiss control, a lucide X named by closeLabel, floored at the 44px target on both axes.Controlled bycloseLabel
Examples
A save confirmation
A button pushes a "Saved" toast that stays a few seconds and then leaves. The words are fictional and name nothing measured.
toast-a-save-confirmation · base base · style base-lyraOpen under the product themeWith an action
actionProps adds one quiet control beside the message, so a reader who removed a note by mistake can reverse it. Here Undo only closes the toast.
toast-with-an-action · base base · style base-lyraOpen under the product themeContent guidelines
Keep a toast to the fact of the action, past tense, a word or two: "Saved", "Reminder set", "Note removed". Never write a clinical word, a reading or a level of urgency into one. Give an action a verb, and make it safe to ignore.
Do
"Saved" with an optional "Undo". The action still happened if the reader looks away.
Don’t
A toast carrying a number, a result or an urgency. Whoever needs it most misses it.
Accessibility
Audited against WCAG 2.2 AA in a source pass and a rendered pass that together raised no axe violation, no target-size hit and no 320px reflow failure. This is an author-run audit, not an independent review; clinical review is pending.
The audit found nothing in the source to change. Roles, names, the polite live region, pause on hover and focus, Escape and focus return all come correctly from @base-ui/react. The X icon is aria-hidden and the close carries a translatable aria-label. Colours are role tokens, type sizes are text-opsin-* tokens, and the controls floor their hit area at --opsin-target-minimum.
Residual items to know. A toast pushed with a description and no title yields a dialog with no accessible name, because ToastList rightly emits no empty title; the default and both examples always pass a title, and a description-only toast is still announced by the live region through aria-describedby. Whether an appearing toast overlaps a control focused elsewhere is a product-layout question outside component scope. Contrast pairs are unmeasured, so the report below carries what was measured.
| Key | Action | Notes |
|---|---|---|
| F6 | Moves focus to the toast viewport | A Base UI global shortcut, so the stack is reachable without tabbing the page. |
| Tab | Moves between the controls in the focused toast | Two stops with an action, one without. Focus stays in the toast. |
| Shift+Tab | Moves back through the controls in the focused toast | The same stops in reverse. |
| Enter, Space | Activates the focused control | The close control or the action, both native buttons. |
| Escape | Dismisses the focused toast | So a reader never waits on the auto-dismiss timer. |
| Pair | Theme | APCA Lc | WCAG 2.2 | Floor |
|---|---|---|---|---|
| body text on the page | light | 101.6 | 17.18:1 | Pass |
| body text on the page | dark | -100.5 | 18.00:1 | Pass |
| body text on a card | light | 104.7 | 17.96:1 | Pass |
| body text on a card | dark | -99.6 | 16.32:1 | Pass |
| body text on the muted ground | light | 98.1 | 16.32:1 | Pass |
| body text on the muted ground | dark | -97.5 | 13.77:1 | Pass |
| secondary text on the page | light | 83.9 | 7.42:1 | Pass |
| secondary text on the page | dark | -80.7 | 13.44:1 | Pass |
| secondary text on a card | light | 87.0 | 7.76:1 | Pass |
| secondary text on a card | dark | -79.8 | 12.19:1 | Pass |
| secondary text on the muted ground | light | 80.4 | 7.05:1 | Pass |
| secondary text on the muted ground | dark | -77.8 | 10.28:1 | Pass |
| a hairline boundary on the page | light | 19.3 | 1.41:1 | Below floor |
| a hairline boundary on the page | dark | -8.2 | 1.90:1 | Below floor |
| a hairline boundary on a card | light | 22.4 | 1.47:1 | Below floor |
| a hairline boundary on a card | dark | -7.3 | 1.72:1 | Below floor |
| a hairline boundary on the muted ground | light | 15.8 | 1.34:1 | Below floor |
| a hairline boundary on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on the page | light | 69.8 | 4.52:1 | Pass |
| a placeholder boundary on the page | dark | -51.0 | 7.62:1 | Pass |
| a placeholder boundary on a card | light | 72.8 | 4.72:1 | Pass |
| a placeholder boundary on a card | dark | -50.1 | 6.91:1 | Pass |
| a placeholder boundary on the muted ground | light | 66.2 | 4.29:1 | Pass |
| a placeholder boundary on the muted ground | dark | -48.1 | 5.83:1 | Pass |
| a placeholder fill on the page | light | 8.3 | 1.18:1 | Below floor |
| a placeholder fill on the page | dark | -8.2 | 1.90:1 | Below floor |
| a placeholder fill on a card | light | 11.4 | 1.23:1 | Below floor |
| a placeholder fill on a card | dark | -7.3 | 1.72:1 | Below floor |
| a placeholder fill on the muted ground | light | 0.0 | 1.12:1 | Below floor |
| a placeholder fill on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on its own fill | light | 58.9 | 3.84:1 | Pass |
| a placeholder boundary on its own fill | dark | -41.5 | 4.01:1 | Below floor |
| the card hairline on the page | light | 69.8 | 4.52:1 | Pass |
| the card hairline on the page | dark | -28.7 | 4.19:1 | Below floor |
| the card hairline on a card | light | 72.8 | 4.72:1 | Pass |
| the card hairline on a card | dark | -27.9 | 3.80:1 | Below floor |
| body text on the canvas material | light | 104.7 | 17.96:1 | Pass |
| body text on the canvas material | dark | -100.5 | 18.00:1 | Pass |
| body text on the inset material | light | 98.1 | 16.32:1 | Pass |
| body text on the inset material | dark | -100.1 | 17.18:1 | Pass |
| body text on the card material | light | 104.7 | 17.96:1 | Pass |
| body text on the card material | dark | -99.6 | 16.32:1 | Pass |
| body text on the raised material | light | 104.7 | 17.96:1 | Pass |
| body text on the raised material | dark | -97.5 | 13.77:1 | Pass |
| body text on the sheet material over the darkest backdrop | light | 99.9 | 16.76:1 | Pass |
| body text on the sheet material over the darkest backdrop | dark | -99.7 | 16.47:1 | Pass |
| body text on the sheet material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the sheet material over the lightest backdrop | dark | -80.8 | 6.07:1 | Pass |
| body text on the overlay material over the darkest backdrop | light | 85.9 | 13.48:1 | Pass |
| body text on the overlay material over the darkest backdrop | dark | -99.9 | 16.77:1 | Pass |
| body text on the overlay material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the overlay material over the lightest backdrop | dark | -54.2 | 2.69:1 | Below floor |
These are the measured token pairs this component draws colour from, not a measurement of the component itself.
API reference
Prop
Type
Generated from ToastProps in registry/bases/base/toast.tsx.
position defaults to bottom-right, limit to three and timeout to 5000ms. Passing timeout={0} stops every toast dismissing itself and raises a development warning, because a message that must persist is an AlertBanner or a Callout. label and closeLabel are props so they can be translated, and both default to English.
Related
- AlertBanner is for a message that needs attention now and stays until the reader deals with it.
- Callout sets a standing note or caveat apart for as long as the reader is on the surface.