Toast
When to use
Section titled “When to use”- Confirming an action: “Saved”, “Message sent”
- Offering a quick Undo after an action
- Background events that need no decision: “Connection restored”
Not for errors that need attention or information that must stay — use an Alert or a Dialog.
Choosing between messages and popups:
| Component | Use it for | For example |
|---|---|---|
| Dialog | Something that needs a decision or input before the user carries on; the page is blocked meanwhile | Confirm a delete, edit a record in a form |
| Drawer | A panel that slides in from an edge and blocks the page; the first choice on a phone | Share options, a mobile nav, a filter panel |
| Popover | Extra content or controls next to a button, while the rest of the page stays usable | Filter panel, share options, details card |
| Menu | A list of actions or settings opened from a button | “More actions ⋯”, a View menu |
| Tooltip | A short text label on hover or focus; nothing in it can be clicked | Name of an icon button, a keyboard shortcut |
| HoverCard | A preview of where a link goes, on hover or focus; nothing essential in it | A profile behind an @mention, a repository’s summary |
| Toast | A brief confirmation after an action, which goes away by itself | “Saved”, “Archived — Undo” |
| Alert | A message that stays in the page until the situation changes | “Your trial ends in 3 days”, a form error summary |
import { Button, Toast, useToastManager } from '@arun-dev/ui';
// The Provider and Viewport go near your app's root, once. Here they wrap the example.export default function ToastBasics() { return ( <Toast.Provider> <Buttons /> <Toast.Viewport /> </Toast.Provider> );}
function Buttons() { const toast = useToastManager();
function archive() { const id = toast.add({ title: 'Conversation archived', type: 'success', action: { label: 'Undo', onClick: () => { toast.close(id); toast.add({ title: 'Restored', timeout: 2000 }); }, }, }); }
function save() { // One toast, updated when the work finishes. const id = toast.add({ title: 'Saving…', timeout: 0 }); setTimeout(() => toast.update(id, { title: 'Saved', type: 'success', timeout: 3000 }), 1200); }
function fail() { toast.add({ title: 'Connection lost', description: 'Your changes will sync when you are back online.', type: 'error', priority: 'high', }); }
return ( <div style={{ display: 'flex', gap: 'var(--space-2xs)', flexWrap: 'wrap' }}> <Button onClick={archive}>Archive</Button> <Button onClick={save}>Save</Button> <Button onClick={fail}>Go offline</Button> </div> );}A toast is a brief message that stacks at the edge of the window, the bottom right by default, and closes itself after a few seconds: “Saved”, “Archived — Undo”.
Set up once
Section titled “Set up once”Put the Provider and one Viewport near your app’s root. Then call useToastManager() anywhere
below to show a toast:
import { Toast, useToastManager } from '@arun-dev/ui';
<Toast.Provider> <App /> <Toast.Viewport /></Toast.Provider>;
function SaveButton() { const toast = useToastManager(); return <Button onClick={() => toast.add({ title: 'Saved', type: 'success' })}>Save</Button>;}What add takes
Section titled “What add takes”| Option | Type | Does |
|---|---|---|
title | ReactNode | the headline |
description | ReactNode | the detail, under it |
type | string | info, success, warning or error colour the edge |
timeout | number | milliseconds before it closes; 0 keeps it until closed |
priority | 'low' | 'high' | high is read out at once (role="alert"); low, when idle |
action | { label, onClick } | a button on the toast, such as “Undo”. It does not close the toast |
onClose | () => void | called once, however it closed |
position | ToastPosition | where this toast shows, instead of the Viewport’s position |
id | string | reuse one to replace a toast |
add returns the id. Pass it to update to change the text when work finishes, or to close.
Position
Section titled “Position”Toasts show at the bottom right by default. Set position on the Viewport to change it for every
toast, and pass position to add to send one toast somewhere else:
<Toast.Viewport position="top-center" />;
toast.add({ title: 'Link copied', position: 'bottom-left' });The six positions are top-left, top-center, top-right, bottom-left, bottom-center and
bottom-right. Toasts at the top slide down as they arrive, and toasts at the bottom slide up.
A toast with its own position joins a stack at that position, which is still part of the same
toast area, so pausing, Esc and screen-reader announcements work the same.
import { useState } from 'react';import { Button, Select, Toast, useToastManager, type ToastPosition } from '@arun-dev/ui';
const positions: ToastPosition[] = [ 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right',];
// The Viewport's `position` is the default for every toast; one toast can still choose its own.export default function ToastPositionExample() { const [position, setPosition] = useState<ToastPosition>('top-center'); return ( <Toast.Provider> <div style={{ display: 'flex', gap: 'var(--space-2xs)', flexWrap: 'wrap' }}> <Select aria-label="Default position" value={position} onChange={(event) => setPosition(event.target.value as ToastPosition)} > {positions.map((p) => ( <option key={p} value={p}> {p} </option> ))} </Select> <Buttons /> </div> <Toast.Viewport position={position} /> </Toast.Provider> );}
function Buttons() { const toast = useToastManager(); return ( <> <Button onClick={() => toast.add({ title: 'Saved', type: 'success' })}> Toast at the default </Button> <Button onClick={() => toast.add({ title: 'Link copied', type: 'info', position: 'bottom-left' })} > Toast at bottom-left </Button> </> );}Choose a default for all toasts in the menu, then show one at the default and one at
bottom-left, which overrides it.
Behaviour
Section titled “Behaviour”- Timers pause while the pointer is over the toasts, while focus is in them, and while the page is hidden. They resume with the time that was left.
- Esc closes the toast that has focus. Focus moves to the toast area, not the page.
- Screen readers hear new toasts politely, or at once with
priority: 'high'. - The newest three show by default (
limit). The rest wait their turn. - The toast area sits in the browser’s top layer, so toasts draw above everything. While a
modal Dialog is open they can be read but not clicked. Everything
outside a modal dialog is inert. Give toasts that may appear then a
timeout.
Your own layout
Section titled “Your own layout”Pass the Viewport a function of the toasts and lay each out with the parts:
<Toast.Viewport> {(toasts) => toasts.map((toast) => ( <Toast.Root key={toast.id} toast={toast}> <Toast.Title /> <Toast.Description /> <Toast.Close aria-label="Dismiss" /> </Toast.Root> )) }</Toast.Viewport>Toast.Provider
Section titled “Toast.Provider”| Prop | Type | Default | Description |
|---|---|---|---|
limit | number | 3 | How many show at once. The rest wait, their timers stopped, and show as others close. |
timeout | number | 5000 | Milliseconds a toast stays, unless it sets its own. 0 keeps toasts until closed. |
Toast.Viewport
Section titled “Toast.Viewport”| Prop | Type | Default | Description |
|---|---|---|---|
closeLabel | string | 'Dismiss' | Accessible name of each toast's close button, in the default rendering. |
position | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'bottom-right' | Where toasts show, unless a toast sets its own position when added. |
children | (toasts) => ReactNode | Lay the toasts out yourself with Toast.Root and its parts. Omit it for the default rendering. | |
className | string | Concatenated with the component's own classes, never replacing them. | |
render | ReactElement | Element or component to render instead of the default. Props, className, event handlers and ref are merged onto it. | |
ref | Ref<HTMLElement> | Ref to the rendered element. Merged with any ref on the render element. | |
…rest | HTMLAttributes | Anything else is spread onto the rendered element, so id, aria-*, data-* and event handlers all reach the DOM. |
- Keep toasts short, and never the only way to do something. A toast can vanish before anyone reaches its Undo, so offer the same action elsewhere.
- For a message that stays on the page, use an Alert.
- Restyle with the
--toast-*tokens.