Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Variants and states

Rest
Neutral
Success
Warning
Danger
Info
Neutral
  • Rest
Success
  • Rest
Warning
  • Rest
Danger
  • Rest
Info
  • Rest

Props

NameTypeDefaultDescription
color'neutral' | 'success' | 'warning' | 'danger' | 'info''neutral'The intent: what the message means. neutral for a plain notice, success for something that went well, warning for something to take care with, danger for something that failed, info for something worth knowing; always in words that say it too. Set on the root as data-color
titleReactNode—The message in a few words, which names the toast: "Link copied". Replaces the HTML title attribute
childrenReactNode—What happened, in a plain sentence, below the title: "Copy it from the address bar instead." Without a title, it names the toast
iconReactNode—Replaces the intent's icon, for the eye only: it is hidden from screen readers. null or false shows no icon; left out, the intent's own shows, and neutral has none. It renders at 13 px, icon.size.325, so give an icon drawn on the 14 px grid, or set --perron-icon-size to its step's icon.size.* through slotProps.icon
actionReactNode—What the person can do about it, such as a small outline Button "Undo", below the sentences. With an action, the toast stays until it is closed, unless autoHideDuration is given
openboolean—Whether the toast is shown. It shows each time open turns true and leaves when it turns false; once it closes itself, set open to false in onClose, so the next true shows it again
defaultOpenbooleanfalseWhether the toast shows when it mounts, when open is left out. It closes itself and does not show again
onClose(event: Event, reason: ToastCloseReason, details: ToastCloseDetails) => void—Called when the toast closes itself: reason is 'close-press' for its close button, 'escape-key' for Esc, 'swipe' for a swipe or 'timeout' once autoHideDuration has passed. Not called when open turns false
autoHideDurationnumber | null5000, or null with an actionHow long the toast shows, in milliseconds, before it hides itself; null keeps it until it is closed. The time pauses while the pointer or the keyboard is in the region, or the window is in the background
closeLabelstringThe locale'sOverrides "Close", the close button's name
refRef<HTMLDivElement>—Reaches the root, the toast
slotProps{ icon?: ComponentProps<"div", {}, HTMLProps> | undefined; content?: ToastContentProps | undefined; title?: ToastTitleProps | undefined; description?: ToastDescriptionProps | undefined; action?: ComponentProps<"div", {}, HTMLProps> | undefined; close?: Omit<ToastCloseProps, "children" | "disabled"> | undefined; }—Props for each part: icon, content, title, description, action, close.
color'neutral' | 'success' | 'warning' | 'danger' | 'info', default 'neutral'
The intent: what the message means. neutral for a plain notice, success for something that went well, warning for something to take care with, danger for something that failed, info for something worth knowing; always in words that say it too. Set on the root as data-color
titleReactNode, default —
The message in a few words, which names the toast: "Link copied". Replaces the HTML title attribute
childrenReactNode, default —
What happened, in a plain sentence, below the title: "Copy it from the address bar instead." Without a title, it names the toast
iconReactNode, default —
Replaces the intent's icon, for the eye only: it is hidden from screen readers. null or false shows no icon; left out, the intent's own shows, and neutral has none. It renders at 13 px, icon.size.325, so give an icon drawn on the 14 px grid, or set --perron-icon-size to its step's icon.size.* through slotProps.icon
actionReactNode, default —
What the person can do about it, such as a small outline Button "Undo", below the sentences. With an action, the toast stays until it is closed, unless autoHideDuration is given
openboolean, default —
Whether the toast is shown. It shows each time open turns true and leaves when it turns false; once it closes itself, set open to false in onClose, so the next true shows it again
defaultOpenboolean, default false
Whether the toast shows when it mounts, when open is left out. It closes itself and does not show again
onClose(event: Event, reason: ToastCloseReason, details: ToastCloseDetails) => void, default —
Called when the toast closes itself: reason is 'close-press' for its close button, 'escape-key' for Esc, 'swipe' for a swipe or 'timeout' once autoHideDuration has passed. Not called when open turns false
autoHideDurationnumber | null, default 5000, or null with an action
How long the toast shows, in milliseconds, before it hides itself; null keeps it until it is closed. The time pauses while the pointer or the keyboard is in the region, or the window is in the background
closeLabelstring, default The locale's
Overrides "Close", the close button's name
refRef<HTMLDivElement>, default —
Reaches the root, the toast
slotProps{ icon?: ComponentProps<"div", {}, HTMLProps> | undefined; content?: ToastContentProps | undefined; title?: ToastTitleProps | undefined; description?: ToastDescriptionProps | undefined; action?: ComponentProps<"div", {}, HTMLProps> | undefined; close?: Omit<ToastCloseProps, "children" | "disabled"> | undefined; }, default —
Props for each part: icon, content, title, description, action, close.

Use it

import { Toast } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Toast" })

Keyboard and ARIA

From the Base UI reference and its source, node_modules/@base-ui/react/toast/. Every key below works while at least one toast is open; with none, the region takes no keys.

KeyDoesFrom
F6From anywhere on the page, moves focus to the region and pauses every toast's timer; focus remembers where it wasBase UI
TabFrom the region, moves to the newest toast, then into it, to its action's controls and its close button, then to the next toast; past the last, back to where focus was before F6Base UI, and the browser inside a toast
Shift+TabMoves back through them; from the region itself, back to where focus was before F6Base UI, and the browser inside a toast
EscapeWith focus in a toast, closes it: onClose is called with 'escape-key', and focus moves to the next toast, or the one before it when it was the last, or back to where it was before F6Base UI; Perron calls onClose
Enter, SpaceOn the close button, closes the toast: onClose is called with 'close-press', and focus moves as for EscapeThe browser and Base UI; Perron calls onClose

Focus leaving the region and the pointer leaving it start the timers again.

ElementRoleAccessible name fromStates announced
viewportregion, a polite live region that reads each toast as it arrivesThe owned "Notifications", through aria-label (CR-3)None
rootdialog, non-modal (aria-modal="false"), in the tab orderIts title through aria-labelledby; without one, its description (CR-9)None
iconHidden: aria-hidden="true"NoneNone
titleParagraph, or a heading when slotProps.title.render makes it oneIts text, from titleNone
descriptionNone (generic div); the root's description through aria-describedbyNoneNone
actionNone (generic div); the caller's controls carry their own rolesThe caller's controls' own namesTheirs
closebutton, native, never aria-hidden (CR-7)The owned "Close", or closeLabel, in the CloseButton's VisuallyHidden spanNone

A toast is read as it arrives because the region is a polite live region already on the page (CR-2): its title, its sentences, its action's names and "Close", in that order. It interrupts nothing. Toast adds no hidden word for its intent, as Alert adds none: the caller's words say what the colour and the icon show.

With no title, the toast is named by its description, the root's aria-labelledby pointing at the description and its aria-describedby left off: a dialog with no name fails axe's aria-dialog-name rule, which the stories run in error mode, a toast such as "Link copied." often has a sentence and no title, and naming it by the sentence and not describing it by the same sentence reads it once.

The close button is never aria-hidden, where Base UI's Toast.Close hides it from screen readers while the region is neither hovered nor holding focus: Toast sets aria-hidden={false} after Base UI's, since a focusable button inside aria-hidden fails axe's aria-hidden-focus rule, which the stories run in error mode, and a person reading the region with a screen reader, without F6, must find the button that closes a toast that stays; the cost is "Close" read after each toast's words as it arrives.

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the colour, the tint and the icon repeat what the caller's words say, and the icon is aria-hidden; the color JSDoc asks for words that say the intent, as Alert's does. A toast that hides itself is read by the region as it arrives, so the ear hears it even where the eye looks elsewhere; one that leaves before it is read pauses while focus or the pointer is in the region.
  • Every landmark and named region has an accessible name: the region is named "Notifications" from the locale, or PerronProvider's notificationsLabel (CR-3); each toast, a dialog, by its title or its description (CR-9).
  • Every default label can be overridden: closeLabel on each Toast, notificationsLabel on PerronProvider for the shared region.
  • Every shape the type accepts renders, in every variant: title, children, icon and action each render a string, an element and an array, as Alert's do; icon as null or false renders no icon part.
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: a toast past Base UI's limit is inert and not displayed; a leaving toast is gone from the list once its transition ends, and Toast keeps no copy of its own.
  • Native semantics first: Base UI's region and dialog roles, a p for the title, a native <button type="button"> to close, the caller's own button or a href in the action; Toast adds aria-labelledby when there is no title (CR-9) and aria-hidden="false" on the close button (CR-7), and no other ARIA.
  • Focus moves in reading order and returns on close: a toast never takes focus when it arrives; F6 reaches the region, Tab walks the toasts from the top, as they stand, newest first, and inside each its action, then its close button, as on screen; closing a focused toast moves focus to the next toast, or to the one before it when it was the last, or back to where it was before F6 (Base UI).
  • Every target is at least size.target-min square: the close button is size.control, 34 px; the action's targets are the caller's components, a small Button size.button-sm.
  • Loops stop when the system asks for less motion: none here; the slide in and out is a transition on duration.toast, which the token's reduced-motion value takes to 0.01 ms.

Owned strings

Toast owns the close button's name, the key en.ts has, and the region's name, a new key. Every other word comes from the caller, through title, children and action, and the icon is hidden from screen readers.

KeyEnglishOverrideSeen or heard where
close"Close"closeLabelclose, heard as the button's name through the VisuallyHidden part; the key en.ts has, whose JSDoc reads "A button that closes a dialog, panel or notice."
notifications"Notifications"notificationsLabel on PerronProviderviewport, heard as the region's name when a screen reader lists landmarks or enters it with F6; new, with the JSDoc "The region at the bottom of the screen that holds the toasts."

The region's name is the new owned key notifications, "Notifications", overridden by a notificationsLabel prop on PerronProvider, which renders the one region, and not on Toast: Base UI's viewport names itself "Notifications" in English in every locale, so Perron replaces it with the locale's word; the region is shared, so a prop on one toast would rename it for every other; and the contract asks for a *Label override for every owned string, which PerronProvider, the region's owner, takes. A Toast with no PerronProvider names its own region with the English word, as every owned string reads without a provider.

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustConfirm what just happened, in a few words: "Link copied", "Filters cleared"Write "Success!" or "Something went wrong": content design has no exclamation marks, and the words should say what happenedreview
DD-2mustKeep the app inside one PerronProvider, so every toast stacks in one region at the bottom of the screen and is read by one live regionRender Toasts with no provider: each gets a region of its own, and two at once overlapreview
DD-3mustSet open to false in onClose, so the next true shows the toast againLeave open true after the toast has closed itself: it doesn't show again until open turns false and truereview
DD-4mustGive a toast an action only for what can't wait, such as "Undo", and let it stay until it is closedGive an action toast a short autoHideDuration: a person still hearing the toast can miss the action, and it may be the only way to undoreview
DD-5mustPut an app-wide context an action needs, such as a router's, around PerronProvider: the toast renders in PerronProvider's region, not where it is writtenRely on a context between PerronProvider and the Toast in the action: the region renders outside itreview
DD-6mustShow a message that must stay while its cause lasts, such as a signal fault, as an Alert in the panelUse a toast for something the person needs later: it leaves after five secondsreview
DD-7mustUse an AlertDialog for a failure that must stop the taskUse a toast to interrupt: every toast is polite, and waits for the screen reader to finishreview