V2 componentsUpdated Nov 3, 2026
Toast
A short confirmation that something just happened, pinned to the bottom centre and gone after five seconds. Use it for results the user caused ("Link copied", "Member removed" with Undo). Always dark in both themes: it is chrome over the page, so unlike Tooltip, which sits on the overlay surface, it does not follow the theme. One shows at a time: a new toast replaces the last in place, and the last waits underneath on its own timer, so dismissing the new one brings it back if it hasn’t expired. Up to three wait; hovering pauses every timer. Swipe one down or right to dismiss it. Never use it for anything the user must act on or can miss: errors that block work belong inline, and decisions belong in a Dialog. An action is at most one ghost button, in practice Undo.
Source: packages/ui/src/components/toast
import { Toast } from '@highlight/ui'Change the props; the preview and the snippet update together.
const toast = Toast.useToast() <Button onClick={() => toast({ message: "Member removed", tone: 'success' })}> Show toast</Button>Wrap the app in Toast.Provider once, then call Toast.useToast() anywhere below it. A message, a message with one action, and either with a leading icon. All are 32px tall, so swapping one for another never moves the toast.
import { Button, Toast } from '@highlight/ui'import { Bookmark } from 'lucide-react' function Buttons() { const toast = Toast.useToast() return ( <div className="flex flex-wrap items-center justify-center gap-3"> <Button variant="secondary" onClick={() => toast({ message: 'Link copied' })}> Message </Button> <Button variant="secondary" onClick={() => toast({ message: 'Member removed', action: { label: 'Undo', onClick: () => undefined } })} > Message and action </Button> <Button variant="secondary" onClick={() => toast({ message: 'Saved to bookmarks', icon: <Bookmark /> })}> Icon </Button> <Button variant="secondary" onClick={() => toast({ message: 'Member removed', tone: 'success', action: { label: 'Undo', onClick: () => undefined }, }) } > Icon and action </Button> </div> )} export default function ToastShapes() { return ( <Toast.Provider> <Buttons /> </Toast.Provider> )}success, info, warning and danger pick the default icon and colour only the icon. The surface never changes, so a danger toast is not a red slab arriving over the page.
import { Button, Toast, type ToastTone } from '@highlight/ui' const TONES: Array<{ tone: ToastTone; message: string }> = [ { tone: 'success', message: 'Changes saved' }, { tone: 'info', message: 'Sync paused while you are offline' }, { tone: 'warning', message: 'Storage is almost full' }, { tone: 'danger', message: 'Could not send the invite' },] function Buttons() { const toast = Toast.useToast() return ( <div className="flex flex-wrap items-center justify-center gap-3"> {TONES.map(({ tone, message }) => ( <Button key={tone} variant="secondary" onClick={() => toast({ message, tone })}> {tone} </Button> ))} </div> )} export default function ToastTones() { return ( <Toast.Provider> <Buttons /> </Toast.Provider> )}An action gets its own toast. Undo returns the request; the button spins while it runs, then the toast becomes the result where it stands, with its timer restarted. If the request fails the same toast shows the error. One toast per thing that happened, never a second one stacked on the first.
import { Button, Toast } from '@highlight/ui' /** Stands in for the request an Undo makes. */function restoreMember(fail: boolean): Promise<void> { return new Promise((resolve, reject) => setTimeout(() => (fail ? reject(new Error('Couldn’t restore Ada. Try again.')) : resolve()), 800), )} function Buttons() { const toast = Toast.useToast() const remove = (fail: boolean) => toast({ message: 'Ada Lovelace removed', action: { label: 'Undo', onClick: (removed) => restoreMember(fail).then(() => removed.update({ message: 'Ada Lovelace restored', tone: 'success', action: undefined }), ), }, }) return ( <div className="flex flex-wrap items-center justify-center gap-3"> <Button variant="secondary" onClick={() => remove(false)}> Remove, then Undo </Button> <Button variant="secondary" onClick={() => remove(true)}> Undo that fails </Button> </div> )} export default function ToastUpdateInPlace() { return ( <Toast.Provider> <Buttons /> </Toast.Provider> )}The root, then any compound sub-components. Every part accepts className and forwards the rest of its props to the element or Base UI part beneath it.
Toast- A single toast surface: a 32px squircle rectangle (not a pill) that holds an icon, the message and an action. Toast.Provider renders these for you. Pass it a container and the toasts mount in that element’s flow instead of pinning to the window, for a surface that owns its toasts and places them itself.
Toast.Provider- Wrap the app. Renders the portal, the live-region viewport and the toasts. limit (how many wait underneath) defaults to 3, timeout to 5000; hovering pauses the timers.
Toast.useToast- Returns toast({ message, tone?, icon?, action?: { label, onClick }, timeout? }), which returns the id. toast.update(id, { ...fields }) rewrites that toast in place and restarts its timer; toast.close(id?) dismisses. An action’s onClick receives the toast: call toast.update to turn "Member removed" into "Member restored" where it stands instead of showing a second toast. Return a promise and the toast waits with the button spinning, then closes or shows the error.
Toast.createToastManager- For use outside React: pass it to Provider as toastManager, and wrap it with toasterFor(manager) to get the same toast(), toast.update() and toast.close() as useToast.
Toast.Viewport / Icon / Description / Action / Close- Low-level parts for custom composition inside Toast, which is the root.