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:

TSX
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:

upload.ts
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:

TSX
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

TSX
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:

TSX
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:

TSX
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:

TSX
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:

TSX
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:

TSX
toast.error('No connection', { dedupeKey: 'offline' });

Shapes

variant changes a toast’s shape. The calls below set it for you.

VariantCallWhere it goesFor
pilltoast() on iOS and the webthe Toaster’s positionShort outcomes; the default with the glass preset
snackbartoast() on Androidthe Toaster’s position, full widthThe Material 3 default on Android
cardtoast.notify()the Toaster’s positionMessages from people: a title, a body, a picture
bannertoast.banner()edge to edge at the top, or the bottom for bottom-* positionsLasting states: offline, maintenance
hudtoast.hud()the middle of the screenA large icon confirming one action
islandtoast.island()in the Dynamic Island, or just below the status barShort system-like events

Notification

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

TSX
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

TSX
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

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

Last updated on Oct 26, 2026

Edit on GitHub