Skip to content
Cynosurev0.5.0
SearchStorybookGitHubnpmColour mode: System. Switch to Light

Components / Overlays

Toast

A short message about something that just happened, shown along one edge of the screen and gone after a few seconds: "Saved", "Copied", "Upload failed". It never takes focus and never blocks the page.

Shows and auto dismisses

When to useLink to "When to use"

  • Confirmation of an action the person just took, when the result is not otherwise visible.
  • A background event worth knowing about now (an upload failed, sync paused).

Use something else when:

  • the message belongs to one part of the page and should stay: a Notice
  • the person must decide something: Dialog
  • the message explains a form error: the field's own error text

UsageLink to "Usage"

Put one Toaster around the part of the app that raises toasts, then call useToast() anywhere inside it.

tsx
Copy
import { Button, Toaster, useToast } from "@arshad-shah/cynosure";

<Toaster position="bottom-end">
  <App />
</Toaster>

function Archive() {
  const { toast } = useToast();
  return (
    <Button
      onClick={() =>
        toast({ title: "Archived 3 files", action: { label: "Undo", onClick: restore } })
      }
    >
      Archive
    </Button>
  );
}

toast() returns an id; dismiss(id) closes that toast and dismiss() closes them all. Each Toaster has its own store, so two products on one page (or two extension surfaces) never share toasts.

Loading, then doneLink to "Loading, then done"

Give a toast an id and call toast() again with the same id to update it in place: the same toast, not a second one. loading: true shows a spinner instead of the tone's icon and keeps the toast until it is updated or dismissed; the update's duration starts afresh.

tsx
Copy
const { toast } = useToast();
toast({ id: "export", title: "Exporting", loading: true });
await exportRows();
toast({ id: "export", title: "Exported 120 rows", tone: "success" });

onDismiss(id, reason) is called once when a toast starts to close: "timeout" when its timer ran out, "user" for the close button or a swipe, "programmatic" for dismiss().

Updates in place from loading to done

StackingLink to "Stacking"

At most max toasts show at once. Older ones fold away and come back as newer ones close; they are the same elements, so a screen reader does not hear them twice.

Max visible older collapse

Keeping clear of fixed UILink to "Keeping clear of fixed UI"

Toasts sit a small margin in from the edges, plus the device's safe area. When the app has something fixed along the edge they sit on, such as a bottom tab bar on a phone, offset moves them further in.

tsx
Copy
<Toaster position="bottom-center" offset="56px">
  <App />
</Toaster>
  • One value is the distance from the top or bottom edge, whichever position names: a step of the space scale ("8") or any CSS length ("72px", "calc(56px + 1rem)").
  • { block, inline } also sets the distance from the side: offset={{ block: "56px", inline: "4" }}. The side follows the reading direction; a centred stack keeps its place and gives up room on both sides.
  • To set it from CSS instead, for example only below a breakpoint, use the tokens --cy-toast-offset-block and --cy-toast-offset-inline on any element around the Toaster. The prop wins over them.
css
Copy
@media (max-width: 48rem) {
  .app {
    --cy-toast-offset-block: 56px;
  }
}
Above abottom tab bar

Inside a DialogLink to "Inside a Dialog"

Toasts raised while a Dialog is open appear above it, and carry on after it closes.

Toasts inside adialog show above it

PropsLink to "Props"

Behaviour and accessibilityLink to "Behaviour and accessibility"

Behaviour and accessibility, table 1
InputResult
Pointer over the toastsTimers pause until it leaves
Focus inside a toast (Tab into it)Timers pause until focus leaves
Close buttonCloses that toast
Action buttonRuns the action; the toast stays until it times out or is closed
  • Announcements: each toast is a live element: role="status" (polite), or role="alert" (assertive) for tone="danger". Its text arrives once, after the live element exists, so it is announced once. An update changes the text in the same live element: only a change is announced, and an update with the same text announces nothing. The loading spinner is decorative.
  • Region: toasts sit in a region named "Notifications" in the browser top layer, so no parent can clip them. Pointer events pass through the empty parts of the region.
  • Edges: the region keeps the toasts inside the device's safe area on every side (env(safe-area-inset-*)), then adds offset. Its empty parts never take presses, so a tab bar under it still works.
  • Hidden page: timers also pause while the tab is hidden, so a toast is not missed.
  • Modal dialogs: while a Dialog or Sheet inside the Toaster is open, the toasts show inside it, above its backdrop, so they stay visible, clickable and announced (outside, the modal would make them inert). When the dialog closes they move back to the page with the time they had left, and are not announced a second time.
  • Motion: toasts slide and fade in and out, and do not move under reduced motion.

TouchLink to "Touch"

Swipe a toast sideways by 64px or more to dismiss it; a shorter swipe springs back. Close and action buttons keep a 44px hit area on coarse pointers.

ThemingLink to "Theming"

Theming, table 2
TokenWhat it sets
--cy-toast-widthWidth of a toast (never wider than the screen)
--cy-toast-radiusCorner radius
--cy-toast-paddingPadding inside a toast
--cy-toast-gapSpace between stacked toasts
--cy-toast-offset-blockExtra distance from the top or bottom edge the toasts sit on
--cy-toast-offset-inlineExtra distance from the side they sit on
ts
Copy
createTheme({ components: { toast: { width: "400px", radius: "10px", offsetBlock: "56px" } } });

Dialog · Button

These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.

Getting started
Theming
Icons
Accessibility
Code editors and highlighters
Using Cynosure without React
Build-tool adapters
Components
Accordion
AppShell
AspectRatio
Breadcrumbs
Calendar
Card
Container
Disclosure
Divider
DocumentViewport
Grid
Inline
PageHeader
PanZoomCanvas
ScrollArea
SideNav
SortableList
SplitPane
Stack
StatusBar
Tabs
TitleBar
Toolbar
Tree
VirtualList
ZoomControls
Announcer
Avatar
Badge
CodeBlock
DescriptionList
EmptyState
ErrorBoundary
Field
Heading
IconTile
Image
Kbd
List
LoadingDots
Notice
ProgressBar
Prose
Skeleton
Stat
Steps
Table
Text
Button
Checkbox
Checklist
ColorField
ColorSwatches
Combobox
CompareSlider
CopyButton
DateField
FileDrop
IconButton
KeyValueEditor
Link
NumberField
Pagination
PathField
RadioGroup
SearchField
SegmentedControl
Select
SelectionFrame
SignaturePad
Slider
Switch
TagInput
TextArea
TextField
ThemeToggle
TimeField
ButtonBase
ChoiceBase
DialogBase
FileInputBase
HiddenValue
Icon
InputBase
LinkBase
LiveRegion
TextAreaBase
ChatMessage
Choice
DataGrid
DeviceFrame
HighlightedText
HotkeyField
ListRow
OptionList
OverlayFrame
QRCode
RowActions
Section
SiteFooter
SiteHeader
SkipLink
SortButton
VisuallyHidden
CommandPalette
ContextMenu
Dialog
FloatingToolbar
HoverCard
Menu
Menubar
Popover
ProgressOverlay
Sheet
Toast
Tooltip
Changelog