Toast
A notice that appears briefly at the edge of the screen and leaves. Use it to report the result of an action without interrupting what the user is doing.
import { ToastProvider, useToast } from 'neba';
// once, around the app
<ToastProvider position="bottom-end">{children}</ToastProvider>;
// anywhere under it
const toast = useToast();
toast.add({ color: 'success', title: 'Deployed', description: 'production · 4m 02s' });Toasts are raised from a hook rather than rendered as a component. The appearance is decided once on ToastProvider; the call site passes only the content.
Props
ToastProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| locale | string | — | BCP 47 tag naming the × on every toast in that language |
| variantshared | 'solid' | 'outline' | 'text' | 'outline' | Weight of the surface. A single toast can override it |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Type scale and padding |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The default colour family |
| densityshared | 'default' | 'compact' | 'default' | Padding only |
| position | `top-${Align}` | `bottom-${Align}` | 'bottom-end' | Where the stack is pinned: top or bottom, times the shared Align. There is deliberately no way to ask for a column down the middle |
| timeout | number | 5000 | How long a toast lasts by default, in ms. 0 keeps it up until it is closed |
| limit | number | 3 | How many are shown at once. The rest are kept and revealed as the stack drains |
| width | number | string | 380 | How wide a toast is allowed to get |
| closeLabel | string | — | Accessible name of every toast's × button |
| classNames | NebaSlots<'viewport' | 'toast' | 'title' | 'description' | 'action' | 'close'> | — | Class names for the parts behind the root. The root itself is className, so there is no root key |
useToast().add(options)
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | The headline |
| description | ReactNode | — | The detail under it. A toast with only this is a one-line toast |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | — | Overrides the provider for this toast alone |
| variantshared | 'solid' | 'outline' | 'text' | — | Overrides the provider for this toast alone |
| icon | ReactNode | false | — | The glyph. Defaults to the one that goes with `color` |
| timeout | number | — | How long this one lasts, in ms. 0 is the right answer for anything the reader has to act on |
| priority | 'low' | 'high' | 'low' | high interrupts a screen reader. An error is worth interrupting for and a save is not |
| actionLabel | ReactNode | — | The label of the action button. Passing it is what makes it appear |
| onAction | (event) => void | — | Called when the action is pressed |
| id | string | — | Reusing an id updates that toast in place and restarts its timer |
| onClose | () => void | — | Called when it closes, however it closed |
| onRemove | () => void | — | Called once it has left the DOM |
Besides add, the hook returns close(id?), update(id, options), promise(promise, { loading, success, error }) and toasts.
Examples
position
Where the stack is pinned, given as one word combining the vertical edge (top/bottom) with NebaAlign.
timeout · actionLabel · onAction
timeout is how long before the toast closes itself. Give a toast the reader has to act on timeout: 0 so it does not leave on its own. actionLabel and onAction add a single button to it.
update and promise
Calling update with the id that add returned refreshes that toast in place and restarts its timer: for a single toast that changes state, like "uploading → uploaded".
const toast = useToast();
const id = toast.add({
title: 'Deleted',
timeout: 0,
actionLabel: 'Undo',
onAction: () => restore(id)
});
toast.update(id, { color: 'success', title: 'Restored' });promise runs the same flow from a single Promise: pass the loading, success and error messages and one toast moves between them.
classNames
A ToastProvider renders no element of its own (it wraps the app and puts a portalled stack on the page), so there is no className here and no root slot for one to land on. Every part of the stack is named instead.
<ToastProvider classNames={{ viewport: 'p-8', toast: 'font-mono' }}>
<App />
</ToastProvider>The slots are viewport, toast, title, description, action and close. viewport is the strip the toasts are stacked in; toast is one of them, and every toast in the stack gets it. See prop conventions for how a class name you pass resolves against the component's own.
Toast or Alert
An Alert belongs to its page and stays there. A toast reports something that just happened and leaves. If the message is still true a minute from now, use an Alert.
Accessibility
- Toasts are announced through a live region, so a message that appeared out of nowhere still reaches a screen reader.
priority: 'high'interrupts what a screen reader is saying; the default waits for a pause.- Timers pause on hover and while the window is blurred. F6 moves focus into the stack.
- The close button stays out of the accessibility tree until the stack is hovered or focused, so a toast is announced as one message rather than as a message and a button.
- The provider's
localenames the × on every toast;closeLabelwrites it out instead.