Toast

A brief, auto-dismissing notification rendered by the Toaster, fired imperatively with toast().

Usage

Mount one Toaster near the root of the app, then call toast() from anywhere: components, stores, or async handlers.

import { Toaster, toast } from '@epilot/spark-ui/toast'
// once, at the app root
function App() {
  return <Toaster />
}

// anywhere
toast('Settings saved')

Use a toast for brief confirmation of an action that just happened ("saved", "removed", "connected"). For messages tied to a spot on the page, use Callout; for anything the user must read or decide on, use Dialog, because toasts auto-dismiss.

Examples

Types

toast.success / info / warning / error pick the matching icon and tinted surface automatically. Rich colors are on by default; pass richColors={false} for neutral surfaces.

With a description

Secondary text below the title, in muted gray on neutral toasts and the tint color on typed toasts. On a typed toast the icon lines up with the title, not with the block as a whole.

With an action

action renders an accent button on the right edge; its onClick runs and the toast dismisses.

Close button

Every toast gets a close button (top-right) by default. Opt out per toast, or with closeButton={false} on the Toaster.

Its accessible name defaults to "Close". Pass closeLabel on the Toaster to translate it for every toast, or on a single toast to describe what that button does.

;<Toaster closeLabel="Schließen" />

toast('Download läuft', { closeLabel: 'Download abbrechen' })

Custom icon

icon replaces the type icon with any node, sized into the same 20px slot. Pass null to render no icon. The icon is hidden from assistive tech, so the title has to make sense without it.

toast('Invoice exported', { icon: <Download /> })

Intercepting the close button

onCloseClick runs before the toast closes. Call event.preventDefault() to keep it open, for example to cancel a running download on the first click and dismiss on the next one.

const id = toast.loading('Downloading invoice', {
  duration: 0,
  onCloseClick: (event) => {
    if (!download.running) return
    event.preventDefault()
    download.cancel()
    toast.info('Download cancelled', { id })
  },
})

Promise

toast.promise shows a loading spinner, then swaps to the success or error state when the promise settles.

Position

position belongs to the Toaster, not to a single toast, so every toast in an app stacks in the same corner. Mount one Toaster and pick the corner there.

<Toaster position="top-right" />
ValueStacks from
"bottom-right" (default)bottom edge, upward
"bottom-left", "bottom-center"bottom edge, upward
"top-right", "top-left", "top-center"top edge, downward

Below sm the corner is ignored. The stack goes full-width and anchors to one edge, defaulting to the top because that is what the end-customer portal does, so a block's toasts and the portal's line up. Narrow your browser to see it. Toasts swipe up, down, or right to dismiss.

Use mobilePosition to anchor it to the bottom instead. Full width stays either way; only the edge changes.

// bottom-right on desktop, top of the screen on mobile (default)
<Toaster />

// bottom-right on desktop, bottom of the screen on mobile
<Toaster mobilePosition="bottom" />

This page has one Toaster at bottom-right, which is why the examples above appear there. There is no live switcher: toast() talks to a single module-level manager, so a second Toaster mounted to demo another corner would render every toast twice.

API Reference

Toaster

Mounts the toast region. Built on Base UI Toast, which owns the stacking, timers, swipe-to-dismiss, and the live region.

PropTypeDefaultDescription
position"top-left" … "bottom-right""bottom-right"Corner the toasts stack in, from sm up
mobilePosition"top" | "bottom""top"Edge the full-width stack anchors to below sm
closeButtonbooleantrueClose button on every toast; a per-toast value wins
closeLabelstring"Close"Accessible name of every close button; a per-toast value wins
richColorsbooleantrueTinted surfaces for success/info/warning/error toasts
durationnumber4000Auto-dismiss time in ms; 0 disables it
limitnumber3Toasts shown at once before older ones collapse behind
classNamestring—Merged onto the viewport

toast

toast(message, options?) shows a neutral toast and returns its id. The typed variants pick the icon and tinted surface.

FunctionDescription
toast.success / info / warning / errorTyped toast with matching icon and tinted surface
toast.loading(message)Spinner toast that stays until dismissed or updated by id
toast.promise(p, msgs)Loading, then success or error, driven by the promise
toast.dismiss(id?)Dismiss one toast, or all when called without an id

Common options (second argument):

OptionTypeDescription
descriptionReactNodeSecondary text below the title
action{ label, ...button }Accent pill on the right edge. Beyond label and onClick it takes any <button> prop, so disabled, className, and type pass through
closeButtonbooleanOverride the Toaster's close-button default
closeLabelstringAccessible name of this toast's close button; overrides the Toaster's closeLabel
iconReactNodeReplaces the type icon; null renders none
onCloseClick(event) => voidRuns on close-button click; event.preventDefault() keeps the toast open
durationnumberPer-toast auto-dismiss time in ms
idstringUpdate an existing toast instead of adding one
priority"low" | "high"high announces assertively
onDismiss() => voidRuns when the toast closes

Accessibility

  • Toasts are announced through a live region, so screen readers pick them up without interrupting the current task. Pass priority: 'high' for messages that must interrupt.
  • Timers pause while the pointer is over the region, and F6 moves focus into the notifications landmark.
  • The close and action buttons stay out of the accessibility tree until the region is focused or expanded, so a screen reader does not announce controls for every transient toast.
  • Don't put the only path to an action in a toast (it auto-dismisses); keep a persistent alternative in the page.

On this page