Toast
A brief confirmation ("3 items removed — Undo") or a non-blocking notice, docked as a full-width bar at the bottom edge.
import { ToastProvider, useToastManager } from '@fairgarden-private/design/components/Toast'
ToastProvider wraps the app and renders the bar: full width at the bottom viewport edge, a white scope that follows the page mode, with a --border-size-2 --primary12 rule on its top edge and content aligned to the page container. It never floats or stacks, and the page reserves no space for it. One toast shows at a time; while more wait, a count reads "1 of 3". It enters and leaves with a clip reveal from the bottom edge, never a fade, and is hidden in print.
useToastManager() adds, updates and closes toasts with the rules applied. Neutral, info and success toasts close after at least 6 s, paused on hover, on focus and while the tab is hidden. Warning, danger and any toast with actions persist until acted on or dismissed, and danger is announced assertively. F6 moves focus to the bar and Esc dismisses the toast.
Offer Undo instead of a confirmation dialog for reversible actions. Errors that need fixing go inline, not in a toast.
Neutral, status and action toasts
Neutral, status and action toasts
status adds the glyph in its status scale: info ○, success ●, warning ▲, danger ◆, each with its inner mark. actions are full-height text cells with title-case labels, divided by rules; they drop to a second row below 768 px. While an action runs, update it with aria-busy and an "-ing…" label.
Trip list: 3 items. Neutral, info and success toasts close after 6 s, paused on hover, focus or a hidden tab; warning, error and action toasts stay.
'use client'
import * as React from 'react'
import { Button } from '@fairgarden-private/design/components/Button'
import { ToastProvider, useToastManager } from '@fairgarden-private/design/components/Toast'
import styles from './statuses.module.css'
function ToastButtons() {
const toasts = useToastManager()
const [items, setItems] = React.useState(3)
const removeItems = () => {
const removed = items
setItems(0)
const id = toasts.add({
title: `${removed} items removed`,
description: 'They left your trip list.',
status: 'success',
actions: [
{
children: 'Undo',
onClick: () => {
// Busy: the cell reads "Undoing…" at its rest width, then the toast closes.
toasts.update(id, { actions: [{ children: 'Undoing…', 'aria-busy': true }] })
window.setTimeout(() => {
setItems(removed)
toasts.close(id)
}, 1200)
},
},
],
})
}
return (
<div className={styles.stack}>
<div className={styles.row}>
<Button variant="solid" onClick={removeItems} disabled={items === 0}>
Remove Items
</Button>
<Button onClick={() => toasts.add({ title: 'Draft saved', description: 'Saved 2 minutes ago.' })}>
Save Draft
</Button>
<Button
onClick={() =>
toasts.add({ status: 'info', title: 'Trail map updated', description: 'The Alder crossing detour is marked.' })
}
>
Show Info
</Button>
<Button
onClick={() =>
toasts.add({ status: 'warning', title: 'Storm warning', description: 'Ridge trails close at 3 pm today.' })
}
>
Show Warning
</Button>
<Button
onClick={() =>
toasts.add({ status: 'danger', title: 'Couldn’t sync your notes', description: 'Check your connection; your notes are kept on this device.' })
}
>
Show Error
</Button>
</div>
<p className={styles.status} aria-live="polite">
{`Trip list: ${items} items. Neutral, info and success toasts close after 6 s, paused on hover, focus or a hidden tab; warning, error and action toasts stay.`}
</p>
</div>
)
}
export function ToastStatuses() {
return (
<ToastProvider>
<ToastButtons />
</ToastProvider>
)
}
API Reference
ToastProvider
Provides toasts to its children and renders the docked bar: full width
at the bottom viewport edge on --layer-5, a white scope with a
--border-size-2 --primary12 top rule, content aligned to
--ds-container-content. One toast shows at a time; a queue count
(“1 of 3″) shows while more wait. The page reserves no space for it.
| Prop | Type | Description |
|---|---|---|
label | | The bar region’s accessible name. Default “Notifications”. |
primary | | Primary Radix scale inside the bar’s |
secondary | | Secondary Radix scale inside the bar. Omitted, the white preset’s default. |
toastManager | | A manager from |
container | | The element the bar’s portal renders into. Default: |
timeout | | Default auto-dismiss time in ms, at least 6000 (--ds-duration-toast). Timers pause on hover, on focus and while the tab is hidden. |
children | |
useToastManager
Adds, updates and closes toasts from inside a ToastProvider, with the §10.17 rules applied: warning, danger and action toasts persist, the rest stay at least 6 s, and danger is announced assertively.
Additional types
ToastAction
An action cell’s button props: its label (children, title case), onClick, aria-busy …
type ToastAction = Omit<React.ComponentPropsWithoutRef<'button'>, 'className' | 'style'>ToastData
What the system keeps on each toast’s data.
type ToastData = { actions?: ToastAction[] }ToastManager
The toast manager with the §10.17 rules applied.
type ToastManager = {
/** The toasts, newest first. */
toasts: BaseToastObject[];
/** Shows a toast and returns its id. Only one shows at a time; the rest queue. */
add: (options: ToastOptions) => string;
/** Changes a toast, such as an action's busy label; persistence is recomputed. */
update: (id: string, options: Omit<Partial<ToastOptions>, 'id'>) => void;
/** Closes one toast, or all of them without an id. */
close: (id?: string) => void;
}ToastOptions
Options for a toast.
type ToastOptions = {
/** Reuse an id to replace a showing toast. */
id?: string;
/** The message's lead, in `type-body-ui` at --font-weight-6 ("3 items removed"). */
title?: React.ReactNode;
/** The rest of the message, in `type-body-ui`; up to 3 lines at base. */
description?: React.ReactNode;
/**
* `info` ○, `success` ●, `warning` ▲ or `danger` ◆, each with its inner
* mark in the status scale. Omitted: the neutral toast, no glyph.
* Warning and danger persist; danger is announced assertively.
*/
status?: ToastStatus;
/**
* Action cells: full-height `type-button` text cells with title-case
* labels ("Undo", "View Order"). A toast with actions persists. While an
* action runs, update it with `aria-busy` and an "-ing…" label ("Undoing…").
*/
actions?: ToastAction[];
/**
* Auto-dismiss time in ms for a neutral, info or success toast without
* actions: at least 6000 (--ds-duration-toast); `0` persists.
*/
timeout?: number;
/** Called when the toast starts closing. */
onClose?: () => void;
/** Called when the toast has left the DOM. */
onRemove?: () => void;
}ToastProviderProps
Props for ToastProvider.
type ToastProviderProps = {
children?: React.ReactNode;
/**
* Default auto-dismiss time in ms, at least 6000 (--ds-duration-toast).
* Timers pause on hover, on focus and while the tab is hidden.
*/
timeout?: number;
/**
* A manager from `Toast.createToastManager()` in
* @base-ui /react, to add toasts from outside React.
*/
toastManager?: ToastManager;
/** Primary Radix scale inside the bar's `white` scope. Omitted, the white preset's default [D133]. */
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple';
/** Secondary Radix scale inside the bar. Omitted, the white preset's default. */
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow';
/** The bar region's accessible name. Default "Notifications". */
label?: string;
/** The element the bar's portal renders into. Default: `document.body`. */
container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null;
}ToastStatus
type ToastStatus = 'info' | 'success' | 'warning' | 'danger'Specification: DESIGN-SYSTEM.md §10.17 (toast), §1.3.1 (docked bars, D25) and §1.5.15 (toast timing).