Rich toasts
Progress bars, steps, expandable details, Undo countdowns, notifications, banners, HUDs and the island.
A toast can hold more than one line of text. Each kind below is one call, and everything that moves (a filling bar, a draining ring) runs on the UI thread, so it stays smooth while your JavaScript is busy.
Info and warning
Two more outcome types, each with its own glyph and colour:
toast.info('A new version is ready', { description: 'Restart the app to update.' });
toast.warning('Storage almost full', { description: '2% left on this device.' });A warning plays the warning haptic; info plays none. With richColors, info is
blue and warning is amber.
Progress
toast.progress shows a toast with a bar under its message and the percentage
at the end of the line, and returns a controller. Set the progress from 0 to 1,
then finish with a success or an error, which replaces the bar in the same
toast:
const upload = toast.progress('Uploading 4 photos');
xhr.upload.onprogress = (e) => upload.set(e.loaded / e.total);
xhr.onload = () => upload.success('4 photos uploaded');
xhr.onerror = () => upload.error('Upload failed');set writes straight to a shared value on the UI thread. Calling it on every
progress event re-renders nothing; the bar eases between values on its own.
Pass 'indeterminate' while the length is unknown, and a sliver sweeps the
track instead.
For work that is already a promise, toast.track creates the toast, hands
your task a function to report progress with, and finishes the toast when the
promise settles:
const file = await toast.track(
(report) => download(url, { onProgress: report }),
{
loading: 'Downloading report',
success: (file) => `Saved ${file.name}`,
error: 'Download failed',
},
);track returns the task’s own promise, so you still get the value or the
error.
A ring instead of a bar
toast.progress('Exporting video', { progressStyle: 'ring' });The ring replaces the icon and fills clockwise from the top. The bar suits toasts with a line or two of text; the ring suits short ones and the HUD.
Steps
For work in named stages, toast.steps shows the current stage’s name and a
bar split into one segment per stage, with “2/3” at the end of the line. The
stages already done are filled and the current one is tinted, since how far
through it the work is isn’t known:
const job = toast.steps(['Compressing video', 'Uploading', 'Processing']);
await compress();
job.next();
await upload();
job.next();
await process();
job.success('Video published');job.go(index) jumps to a stage; job.step is the current one, from 0. A
screen reader hears each stage as “Uploading, step 2 of 3”. Once the job has
an outcome, a late next() or go() does nothing, so the success toast is
never turned back into a stage.
Expandable details
A chevron opens more content below the message. While it is open, the Toaster’s timers stop, so the details stay as long as someone is reading them:
toast.error('3 photos failed to upload', {
expandable: {
content: 'IMG_2041.jpg, IMG_2042.jpg and IMG_2045.jpg are larger than 50 MB.',
},
});content can be any element. defaultExpanded: true opens it from the start,
and label changes what screen readers call the toggle (by default “Show
details” and “Hide details”).
Undo with a countdown
countdown: true on an action draws a ring around it that drains over the
time the toast has left, as Gmail’s Undo does. It stops while the queue is
paused:
toast('Conversation deleted', {
duration: 6000,
action: { label: 'Undo', countdown: true, onPress: restore },
});Cancel and confirm
cancel adds a quieter button before the action, for a question with two
answers:
toast('Delete 3 photos?', {
duration: 8000,
cancel: { label: 'Cancel', onPress: () => {} },
action: { label: 'Delete', color: '#FF3B30', onPress: deletePhotos },
});Both buttons dismiss the toast after their onPress.
Repeats
A toast with a dedupeKey shows once however often it is called. Each repeat
while it is on screen adds to a count on the toast, shown as ×2, ×3 and so on:
toast.error('No connection', { dedupeKey: 'offline' });Shapes
variant changes a toast’s shape. The calls below set it for you.
| Variant | Call | Where it goes | For |
|---|---|---|---|
pill | toast() on iOS and the web | the Toaster’s position | Short outcomes; the default with the glass preset |
snackbar | toast() on Android | the Toaster’s position, full width | The Material 3 default on Android |
card | toast.notify() | the Toaster’s position | Messages from people: a title, a body, a picture |
banner | toast.banner() | edge to edge at the top, or the bottom for bottom-* positions | Lasting states: offline, maintenance |
hud | toast.hud() | the middle of the screen | A large icon confirming one action |
island | toast.island() | in the Dynamic Island, or just below the status bar | Short system-like events |
Notification
toast.notify({
title: 'Maya Patel',
body: 'Sent you 4 photos from the trip',
avatar: { uri: maya.photoUrl },
timestamp: 'now',
onPress: () => router.push(`/chat/${maya.id}`),
});avatar takes an image source or any element, and replaces the icon.
onPress makes the whole toast a button that dismisses after it runs.
Banner
toast.banner("You're offline", {
type: 'warning',
description: 'Changes will sync when you reconnect.',
duration: 'infinite',
});Banners default to type: 'info'. They span the screen and slide from the
edge rather than popping in.
HUD
toast.hud('Added to Library'); // success by default
toast.hud('Saving', { type: 'loading' });The HUD draws a large icon above the message and scales in where it is.
Island
toast.island('AirPods Pro connected', { type: 'success' });A black capsule, in either appearance, as the island itself is always black. On iPhones with a Dynamic Island it grows down out of the island and keeps its content below the camera, as an expanded Live Activity does. Elsewhere it sits just below the notch or status bar.
Everything at once
These options combine. A toast can be a card with an avatar, a progress bar and an action; a banner with a cancel button; a HUD with a ring. The example app’s “Rich toasts” section shows each kind above on iOS and Android.
What did you think of this content?
Glad it helped. A star on GitHub is how other people find it.
Thank you. The issue that just opened goes straight to the maintainer; say what was missing and the page gets fixed.