Toast
A notice that arrives because something happened, and leaves on its own.
Overview
A notice that arrives because something happened, and leaves on its own.
First example
<XAUIProvider><ToastHost placement="bottom" duration={4000} maxVisible={3}>{app}</ToastHost></XAUIProvider>
Anatomy
;<ToastHost>{app}</ToastHost>
// anywhere below it
const { toast } = useToast()
toast({
render: () => (
<Toast variant="success">
<Toast.Title>Enregistré</Toast.Title>
<Toast.Description>Vos modifications sont sur le serveur.</Toast.Description>
<Toast.Actions>
<Toast.Close asChild>
<Button size="sm">Fermer</Button>
</Toast.Close>
</Toast.Actions>
</Toast>
),
})
ToastHost— the queue, the timers and the stack. Mounted once.useToast— how anything asks for one.Toast— the card. Presentational, and replaceable.Toast.Title— what happened. The one node the variant colours.Toast.Description— the detail.Toast.Actions— the row of things you can do about it.Toast.Close— anything that sends this one away early.
Usage
The host
<XAUIProvider>
<ToastHost placement="bottom" duration={4000} maxVisible={3}>
{app}
</ToastHost>
</XAUIProvider>
It renders into the nearest PortalHost, so the stack sits over navigation rather than
inside whatever screen asked for it.
One that waits for an answer
toast({ duration: 0, render: … })
0 keeps it until something dismisses it — for the toast that asks a question rather than
reporting an answer.
Dismissing early
const id = toast({ render: … })
dismiss(id)
render is also handed { dismiss }, which is the same thing without holding the id.
Root props
ToastProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
| variant | ToastVariant | undefined | No | — |
| radius | RadiusKey | undefined | No | — |
Slots
ToastActionsProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
ToastCloseProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
| asChild | boolean | undefined | No | — |
ToastDescriptionProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
ToastTitleProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
Variants, sizes and colour
The variant paints the title, and nothing else
A red card sliding in from the edge of the screen reads as the app breaking; a red line of text reads as the thing you just did failing. The surface stays the theme's floating one whatever happened — which is also what lets two toasts of different kinds stack without the pile looking like a paint chart.
It uses the soft foregrounds rather than the full colours: a toast is read at a glance
and from the corner of the eye, and danger at full strength on an overlay surface is a
shout where the soft one is a statement.
The description stays muted whatever the variant. The title already said in colour what kind of thing happened, and saying it twice leaves nothing for the eye to rank.
Accessibility
The card is an alert with accessibilityLiveRegion="polite", so it is announced when it
arrives rather than when the reader reaches it — a toast that waits its turn in the reading
order has usually gone by the time it is read.
useToast outside a host warns and does nothing rather than throwing. A missing host is
a setup mistake in the app shell, and a screen that crashes on its way to reporting that a
save succeeded has turned a good outcome into a bad one.
Migration from legacy
Implementation notes
The split, and why it is the whole design
Toast does not know it is in a queue, when it will leave, or what is stacked under it.
The host owns all three.
That is what lets render return anything at all — the queue never looks at it. Toast is
the card this library ships, not the card the host requires.
Toast.Close still knows which toast it belongs to without being told: the host provides
the dismiss around each entry, and the card folds it into the context its slots read. A
close button two levels down needs nothing passed to it.
The stack
The cards are not laid out one under another. Every one is anchored to the same edge, and its depth is entirely in its transform, so a pile of eight costs the height of one:
| depth | translateY | scale |
|---|---|---|
| 0 (front) | 0 | 1 |
| 1 | 10 toward the edge | 0.97 |
| 2 | 20 | 0.94 |
Both numbers are the reference implementation's, read off their toast.animation.ts: translateY: [0, 10] and
scale: [1, 0.97], interpolated over the index. The shoulder a card leaves is
10 − 0.03 × height, around 7 points on a two-line toast — enough to say there is another
one, not enough to be read as a second card.
The ladder does not stop. Their interpolation clamps the front side only, so the fourth
card is genuinely further back than the third rather than sitting exactly on it and reading
as one. What ends the ladder is maxVisible, past which the card is transparent.
Newest is painted last, so it lands in front without a zIndex. A card past maxVisible
takes no touches: a transparent card is still a target, and a press meant for the front one
must not land on something nobody can see.
The swipe
The front card is thrown away by dragging it away from its edge — up on a top stack, down on a bottom one. The pile empties one card at a time, each swipe promoting the next.
It goes past 50 points or 500 points a second, the reference implementation's thresholds, and either alone is enough: distance without velocity refuses a flick that clearly meant it, velocity without distance refuses a slow deliberate push. A toast is glanced at, so both readings count.
Dragged the wrong way it resists rather than refuses — the whole screen's travel maps onto 40 points, so the card answers the finger without pretending it can go there. Under a finger it sinks half a percent, which is meant to be felt rather than seen.
The throw carries on at the speed the finger left it (withDecay, velocity × 1.5) and the
record goes a moment later, so a hard flick leaves faster than a soft one and neither is cut
off at the frame the finger lifted.
Only the card in front. The ones behind show a seven-point shoulder — a target under any reasonable minimum, and dragging the second card out from under the first reads as a glitch rather than as a dismissal.
The gesture runs on react-native-gesture-handler, an optional peer of this package.
It is reached only through @xaui/native/toast, so a project that never imports a toast
never loads it. Set isSwipeable={false} to leave the pile alone.
Toast
| prop | type | default | description |
|---|---|---|---|
variant | ToastVariant | default | Colours the title only |
radius | RadiusKey | — | Overrides the card's corner |
default · accent · success · warning · danger.
Motion
It slides from the edge it will sit against, and leaves the same way — 260 ms in, 180 ms out.
That is what separates it from every other overlay here. A dialog and a popover appear where they are, because they were asked for; a toast arrives, because something happened. Motion across the screen's edge is the difference between the two.
It replaces `Snackbar`
The legacy component is Snackbar. It is the same object under two names, and the reference implementation calls
it toast; the roadmap's P5.18 closes with this.
| Legacy | v1 |
|---|---|
<Snackbar message="…" /> | <Toast.Title>…</Toast.Title> |
action={{ label, onPress }} | a Button inside <Toast.Actions> |
duration | duration on the call, or on the host |
themeColor="danger" | variant="danger" |