Toast
A message that arrives on its own, over whatever is already on the page.
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' });ToastProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| 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 | 'Close' | Accessible name of every toast's × button |
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
Where the stack sits
position is two words rather than a side plus an align, because the two are not independent: a toast stack is pinned to the top or the bottom and never to a side. The second half is NebaAlign, the same word every other component uses.
Actions, and following a promise
A toast the reader has to act on should not leave before it is read, so give it timeout: 0. promise is the other half of the same idea: one toast that changes its mind rather than three stacked on each other.
A hook, not a component
What a caller has at the moment a toast is warranted is a click handler, not a place in the tree. A <Toast open={…}/> they would have to keep mounted — with a piece of state per message — is the shape this component exists to avoid.
Everything about how a toast looks is decided once, on the provider: where the stack sits, how wide it is, which surface it wears, how long it lasts. The call site stays the one thing it should be — what happened.
const toast = useToast();
const id = toast.add({
title: 'Deleted',
timeout: 0,
actionLabel: 'Undo',
onAction: () => restore(id)
});
toast.update(id, { color: 'success', title: 'Restored' });Reusing an id updates that toast in place and restarts its timer, which is what "uploading… / uploaded" wants.
Toast or Alert?
An Alert belongs to the page it is about and stays there. A toast is about something that just happened somewhere else, and it leaves. If the message is still true a minute from now, it is an Alert.
Accessibility
Base UI owns the parts that are invisible when they work: the live region that makes a message which appeared out of nowhere reach a screen reader, timers that pause on hover and on window blur, the limit, the swipe, and F6 to move focus into the stack.
priority: 'high' interrupts a screen reader; the default waits for a pause. An error is worth interrupting for and a save confirmation is not.
The × is deliberately kept 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.