Skip to content

Toast

A message that arrives on its own, over whatever is already on the page.

tsx
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

PropTypeDefaultDescription
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
timeoutnumber5000How long a toast lasts by default, in ms. 0 keeps it up until it is closed
limitnumber3How many are shown at once. The rest are kept and revealed as the stack drains
widthnumber | string380How wide a toast is allowed to get
closeLabelstring'Close'Accessible name of every toast's × button

useToast().add(options)

PropTypeDefaultDescription
titleReactNodeThe headline
descriptionReactNodeThe 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
iconReactNode | falseThe glyph. Defaults to the one that goes with `color`
timeoutnumberHow 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
actionLabelReactNodeThe label of the action button. Passing it is what makes it appear
onAction(event) => voidCalled when the action is pressed
idstringReusing an id updates that toast in place and restarts its timer
onClose() => voidCalled when it closes, however it closed
onRemove() => voidCalled 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.

tsx
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.

Released under the MIT License