DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
AlertBannerToastEmptyStateTooltipCoachMark
AI agentsContributing
  1. Components
  2. Toast

Toast

Transient floating notifications built on the Base UI Toast primitive — variants, stacking, promise flows, timer pause, swipe and full keyboard access.

$ pnpm add @tessinaui/ui

When to use

  • The outcome of an action just taken. "Saved", "Copied", "Message archived": confirmation in a few seconds, without moving the user's focus.
  • One quick follow-up. An inline action such as Undo, which holds the toast longer so it stays reachable (With action).
  • Background work with a result. promise() shows a loading toast and swaps it for the success or error content when the work settles.
  • Short copy. A title, at most a line of description, and nothing the user has to study. For a single line, variant="pill" is built for it.
  • Anything that can be reached again. A toast clears itself, so the same information or action must also exist somewhere permanent.

When not to use

  • For the state of this page or section. Use Alert, which stays until resolved or dismissed.
  • For site-wide announcements. Use Banner.
  • For a decision that must interrupt. Use AlertDialog; a toast never takes focus.
  • As the only path to information or an action. It is ephemeral by design.
  • For a long message. If it needs reading time or a second action, it belongs in a surface that stays.
  • For several at once. Stack only what the user needs; repeated actions should share a stable id (Deduplication & update).

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { Toast, ToastProvider, Toaster, useToast } from "@tessinaui/ui/toast";

Setup

Wrap your app (or layout) with ToastProvider and place Toaster where you want notifications to appear. Mount them once, at the root — the viewport is the live region, and it must exist before toasts are fired.

example.tsx
export default function RootLayout({ children }) {
  return (
    <ToastProvider limit={3}>
      {children}
      <Toaster position="bottom-right" />
    </ToastProvider>
  );
}

Firing a toast

example.tsx
function MyComponent() {
  const { toast } = useToast();

  return (
    <button onClick={() =>
      toast({
        title: "Changes saved",
        description: "Your profile has been updated successfully.",
        intent: "success",
        action: { label: "Undo", onClick: handleUndo },
      })
    }>
      Save
    </button>
  );
}

Promise lifecycles

example.tsx
const { promise } = useToast();

promise(saveDraft(), {
  loading: "Saving…",
  success: (draft) => ({ title: "Saved", description: draft.name }),
  error: (e) => ({ title: "Save failed", description: String(e) }),
});

Outside React

createToastManager() gives you the same API in plain modules — wire its .manager into the provider:

example.tsx
// toasts.ts
import { createToastManager } from "@tessinaui/ui/toast";
export const appToasts = createToastManager();

// layout.tsx
<ToastProvider toastManager={appToasts.manager}>…</ToastProvider>

// anywhere, no hook needed
appToasts.toast({ title: "Session expired", intent: "warning" });

Duration rules

CaseDefault
Plain toast5000 ms (provider timeout)
intent="error"0 — persists until dismissed
Has action10000 ms — the action stays reachable
Explicit durationalways wins (0 = persistent)

Toasts are ephemeral. Never make one the only path to information or an action — the same content must be reachable somewhere permanent.

Examples

Default

A basic toast with a title, description, and dismiss button. Timers pause while you hover or focus the stack, and resume when you leave.

Intents

Semantic colors and icons for success, error, warning, and info. Error toasts announce assertively (a visually-hidden role="alert" clone) and persist until dismissed; everything else announces politely.

Variants

Four materials: card (default floating panel), tinted (the intent paints the plate), inverse (the always-dark overlay plate — the snackbar), and pill (a single-line capsule that sizes to its content, ignores rounded, and drops description).

Sizes

Compact sm and standard md, plus the independent width cap.

With action

Pair a toast with an inline CTA. Action-bearing toasts default to a 10s duration (instead of 5s) so the action stays reachable — and the same operation must also exist somewhere permanent in your UI, because a toast is ephemeral by design.

Undo

The classic destructive-action follow-up: an inverse snackbar whose Undo is a regular action button (the on-color chip on the dark plate).

Promise

promise() binds a toast to an async lifecycle: a persistent loading toast with a spinner, swapped in place for the success or error content when the promise settles. The error phase persists until dismissed.

Stacking

Toasts collapse into a peeking stack; hover, focus, or F6 expands it. limit on the provider caps how many are on screen — the overflow stays mounted (data-limited, inert) and resurfaces as newer toasts leave.

Positions

Six anchors. Below the sm breakpoint every anchor renders as a full-width strip on its edge — the dominant phone presentation. Swipe-to-dismiss is derived per position: away from the anchor edge, plus toward the nearer side.

Persistence & dismissal

Errors persist by default; duration: 0 makes anything persistent; dismissible={false} removes the close button; closeLabel localizes it.

End content

endContent is a free-form trailing slot rendered beside action. When a close button is also present, the trailing row moves below the text and wraps rather than overflowing. Match the toast's rounded and button size on your own controls — the toast renders its action at xs (size="sm") or sm (size="md").

Deduplication & update

Pass a stable id to keep a repeated action from stacking duplicates (collisionBehavior: "replace" | "ignore"), or change an on-screen toast with update() — content, intent, and duration swap in place without replaying the enter animation.

Title only

A minimal toast that omits the description for short, single-line feedback — or reach for variant="pill", which is built for exactly this.

RTL

Per-toast dir (or defaultDir on the Toaster) mirrors the layout — icon, text and controls flip; closeLabel localizes the dismiss button.

On color

A fired toast mounts in the Toaster's portal, so it cannot inherit a tone from the page that triggered it. variant="inverse" is the toast's on-color plate: it publishes its ink, and the action and close button dress from it. Every other variant publishes the default tone.

Loading

Toast-shaped skeleton placeholders for queued or loading notifications. The control bars derive from the real button variants, so sizes, radii and the mobile touch floors always match.

API Reference

Toast

The static visual card — for rendering toast anatomy inline (docs, previews, custom viewports). Fired toasts render the same anatomy automatically.

PropTypeDefaultDescription
titleReactNode—Heading line
descriptionReactNode—Supporting body text (ignored by variant="pill")
intent"none" | "error" | "warning" | "success" | "info""none"Semantic color and default icon
variant"card" | "pill" | "tinted" | "inverse""card"Visual material
size"sm" | "md""md"Compact or standard density
width"narrow" | "default" | "wide""default"Max width of the card. A fired toast only narrows within the Toaster's column; widen the column with defaultWidth
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner radius (nested buttons follow it; full is the panel cap, the pill variant is the capsule)
iconReactNode—Overrides the default intent icon
showIconbooleantrueShow/hide the leading icon
actionToastAction—Inline CTA button (one action max)
endContentReactNode—Trailing custom content, rendered after action
dismissiblebooleantrueRender the dismiss (×) button
closeLabelstring"Dismiss notification"Accessible name of the dismiss button
onClose() => void—Renders the dismiss button and receives its click
dir"ltr" | "rtl"—Text direction
classNamestring—Extra class names

Plus every standard div attribute — style, data-* and aria-* are forwarded to the root element.

ToastAction

PropTypeDefaultDescription
labelstring—Button label
onClick() => void—Click handler (does not auto-dismiss)
variantButtonVariant"primary"Button style — "ghost" reads as an inline text action

ToastProvider

PropTypeDefaultDescription
limitnumber3Max toasts on screen; overflow collapses (data-limited, inert) and resurfaces
timeoutnumber5000Default auto-dismiss ms (0 = persistent)
toastManagerToastManager—Manager from createToastManager() for imperative use

Toaster

PropTypeDefaultDescription
positionToastPosition"bottom-right"Screen anchor for the stack
expandbooleanfalseKeep the stack expanded instead of the collapsed peek
labelstring"Notifications (F6)"Accessible name of the notifications region
defaultVariantToastVariant"card"Fallback variant
defaultSize"sm" | "md""md"Fallback size
defaultWidthToastWidth"default"Fallback width (also sizes the stack column)
defaultRoundedToastRounded"md"Fallback corner radius
defaultDir"ltr" | "rtl""ltr"Fallback direction
maxVisiblenumber—Deprecated — use limit on ToastProvider; this prop is inert
classNamestring—Extra class names on the viewport

useToast

example.tsx
const { toast, update, promise, dismiss, dismissAll, toasts } = useToast();
ReturnTypeDescription
toast(options: ToastOptions) => stringShows a toast; returns its id
update(id, options: Partial<ToastOptions>) => voidChanges an on-screen toast in place
promise(p, { loading, success, error }) => PromiseLoading → success/error toast bound to a promise
dismiss(id: string) => voidCloses a toast by id
dismissAll() => voidClears all toasts
toastsToastObject[]Current toast stack (Base UI shape)

ToastOptions

Everything Toast accepts visually, plus:

PropTypeDefaultDescription
idstringautoStable id for dedupe; also what dismiss()/update() take
collisionBehavior"replace" | "ignore""replace"What to do when this id is already on screen
durationnumbersee Duration rulesms before auto-dismiss; 0 = persistent
onDismiss() => void—Fired once when the toast closes, by any path
onRemove() => void—Fired after the exit transition, on unmount

ToastSkeleton

PropTypeDefaultDescription
variantToastVariant"card"Matches the toast it stands in for
size"sm" | "md""md"Density
widthToastWidth"default"Card width
roundedToastRounded"md"Corner radius
showIconbooleantrueLeading icon placeholder
showTitlebooleantrueTitle bar placeholder
linesnumber1Description line placeholders
showActionbooleanfalseAction-button placeholder
showEndContentbooleanfalseTrailing end-content placeholder
showDismissbooleantrueClose-button placeholder

Position values

ValueDescription
"top-left" / "top-center" / "top-right"Top anchors — toasts enter/exit upward, swipe up
"bottom-left" / "bottom-center" / "bottom-right"Bottom anchors (default bottom-right) — enter/exit downward, swipe down

Accessibility

The behavior layer is the Base UI Toast primitive; the contract below is pinned by the component's test suite.

Pattern. The viewport is a named role="region" live area (aria-label="Notifications (F6)", polite). Each toast renders as a non-modal dialog labelled by its title and described by its description; intent="error" toasts are priority: high — announced assertively through a visually-hidden role="alert" clone, without stealing focus. No toast ever takes focus when it appears.

Keyboard.

KeyAction
F6Move focus into the notifications viewport (expands the stack; the newest toast shows the focus ring); leaving restores prior focus
Tab / Shift+TabCycle through toasts and their controls; exiting the viewport returns focus to where you were
EscClose the focused toast

Timing (WCAG 2.2.1). Auto-dismiss timers pause on hover, on focus, on window blur, and while touching a toast — and resume on leave. Errors persist until dismissed. Action-bearing toasts hold for 10s.

Swipe is a pointer convenience, never the only path — the close button, Esc and the timeout remain.

Reduced motion. Enter/exit transitions and stack movement are disabled under prefers-reduced-motion: reduce.

Your part. A toast is ephemeral: any action inside one must also exist somewhere permanent, and critical errors that demand a decision belong in a dialog, not a toast. Pass closeLabel and label when localizing.

AT status: VoiceOver smoke pending for this release. NVDA and JAWS: UNTESTED (no Windows host) — the component stays beta until passes are recorded.

BannerEmptyState

On this page

When to useWhen not to useInstallationUsageSetupFiring a toastPromise lifecyclesOutside ReactDuration rulesExamplesDefaultIntentsVariantsSizesWith actionUndoPromiseStackingPositionsPersistence & dismissalEnd contentDeduplication & updateTitle onlyRTLOn colorLoadingAPI ReferenceToastToastActionToastProviderToasteruseToastToastOptionsToastSkeletonPosition valuesAccessibilityKeyboard.