Skip to main content

Toast

Implemented

Ephemeral notification card — 4 variants (success/info/warning/error). Card-only: positioning, stacking and dismissal timing belong to the consuming container (no DS container molecule yet). error announces via role='alert' + aria-live='assertive'; the other variants via role='status' + aria-live='polite'.

Categorymolecules
Sourcepatterns/molecules/toast.html
AI HintEphemeral confirmation — use for transient outcomes; positioning, stacking and auto-dismiss belong to the consuming container (not yet shipped as a DS molecule). For persistent feed rows use Notification; for inline blocking errors use Alert.

Ephemeral notification card — 4 variants (success/info/warning/error). Card-only: the molecule announces itself and fires onDismiss(id); positioning, stacking, and dismissal timing belong to the consuming app (a DS-side container is not shipped yet).

Toast

Feedback molecule — Family 4

A single toast card: icon, title, optional description, optional inline action, dismiss button. Presentation-only — it never unmounts itself and has no timer. The consuming app owns the overlay position, the stack, and removal. The ToastMolecule export is a plain alias of this component; there is no separate molecule.

Changes saved

Your event details were updated.

New ticket tier available

Premium seats opened for Summer Fest 2026.

Inventory running low

Only 12 tickets left for tonight.

Order removed

Order #4821 was deleted.

This component supports light and dark mode.

Usage

A Toast is a self-announcing message card. It has no position, no stack, and no timer — mount it inside your own overlay layer and remove it when onDismiss(id) fires. id, variant, and title are required with no defaults — a bare <Toast /> crashes at render.

import { Toast } from '@epasslive/design-system'

<Toast
  id="evt-2026-saved"
  variant="success"
  title="Changes saved"
  description="Your event details were updated."
  onDismiss={removeToast}
/>

<Toast
  id="pay-retry"
  variant="error"
  title="Payment failed"
  description="Your card was declined."
  action={{ label: 'Retry', onClick: retryPayment }}
  onDismiss={removeToast}
/>

The barrel also exports ToastMolecule — a plain alias of Toast, both pointing at the same module. Use whichever name matches your import style; the alias never diverges.

Alert vs. Toast vs. Notification

Three feedback surfaces overlap; pick by persistence and who owns the announcement. Rule of thumb: in-flow status → Alert, transient overlay → Toast, durable per-user feed item → Notification.

Pattern
Best for
Dismissal
Announcement (live region)
Alert
Inline message in page flow — form validation, save confirmations, system messages next to the content they describe.
Optional, manual (dismissible + onDismiss).
Self-announcing: owns role="alert" (error) / role="status" (others).
Toast
Ephemeral overlay feedback — confirms or flags an action from anywhere on the page. Card-only today: positioning, stacking, and timing are the consuming app’s job.
Manual only (dismiss button → onDismiss(id)). Auto-dismiss is a future-container concern — none ships yet.
Self-announcing: owns role="alert" + aria-live="assertive" (error) / role="status" + aria-live="polite" (others).
Notification
Persistent feed row in a notification center / bell dropdown — stays until the user reads or dismisses it.
Manual, per-row (onDismiss).
Announced by its container: the row is role="article"; the surrounding list owns aria-live="polite".

If the message must persist in the layout (no overlay), it is an Alert or a Notification — not a Toast. If you only need the unread count on the bell trigger, that is a Badge.

Container contract (not yet shipped)

The molecule ships as a card only — there is no ToastContainer, no queue, and no auto-dismiss anywhere in the DS today. Your app owns the overlay: render toasts in a fixed viewport region and remove them on onDismiss(id). The card already applies pointer-events-auto, expecting a pointer-events-none viewport parent.

When a DS-side container lands, it must satisfy at minimum: layer z-ep-toast (70, above popovers — currently misused by Tooltip; fix scheduled); auto-dismiss default ~5s with pause on hover/focus (WCAG 2.2.1); Escape dismisses the focused toast; focus return to a sensible target after dismiss; and forwarding of the (today inert) duration prop.

Accessibility

Announcement is variant-driven, per the WAI-ARIA APG: variant="error" renders role="alert" + aria-live="assertive" so screen readers interrupt current output; every other variant renders role="status" + aria-live="polite" and waits. aria-atomic="true" is always set.

The variant icon is aria-hidden — the visible title and description carry the message. The dismiss button is a native <button> labeled via dismissLabel (English default "Dismiss notification" — pass an explicit label on localized pages) with a teal focus-visible ring. The root mirrors data-toast-id and data-toast-variant for end-to-end tests.

Known gap (scheduled fix): the dismiss (28×28px) and action buttons sit under the 44px --ep-tap-min canonical tap target.

Dark mode

The card uses bg-ep-surface-card, ring-ep-overlay-border-subtle, and shadow-ep-md — all flip automatically with the theme. Left-border accents and icons use semantic tokens (ep-semantic-success / ep-semantic-warning / ep-semantic-error; info uses ep-teal + ep-text-teal) with dark overrides, so contrast holds in both themes. Icons are Phosphor SSR glyphs colored per variant by token classes.

Props

Prop
Type
Default
Description
id*
string
—

Stable identifier for this toast. Forwards to data-toast-id on the root — the consuming app targets dismissal and updates by it.

variant*
ToastVariant
—

Semantic tone: "success" | "info" | "warning" | "error". Drives the left-border accent, the icon glyph, and the announcement role (error differs — see Accessibility).

title*
string
—

Headline text. Read first by assistive tech.

description
string
—

Optional supporting copy rendered under the title.

action
{ label: string; onClick: () => void }
—

Optional inline action — teal text button under the message. Clicking it calls onClick and then onDismiss(id); the app owns the actual removal.

duration
number
—

Declared but not currently forwarded to anything (no timer exists anywhere in the DS). Reserved for the future container contract — do not rely on it yet.

onDismiss
(id: string) => void
—

Called with the toast id when the dismiss button (or an action button) is clicked. The molecule never unmounts itself — the app removes the toast.

dismissLabel
string
—

Accessible label for the dismiss button. Defaults to the English "Dismiss notification" — pass an explicit label on non-English pages.

className
string
—

Additional classes appended to the toast root.

Props

Preview

Toast

Prop
Type
Required
Default
Description
id
string
Required
—

Forwards to data-toast-id — unique per toast so a container can target dismiss/update by id.

variant
'success' | 'info' | 'warning' | 'error'
Required
—

Visual and announcement variant; error switches the card to role='alert' / aria-live='assertive'.

title
string
Required
—

Headline text of the card.

description
string
Optional
—

Secondary body copy under the title.

action
{ label: string; onClick: () => void }
Optional
—

Inline text action (e.g. Retry). onClick carries no payload — close the toast via onDismiss.

duration
number
Optional
—

Reserved for the future container contract — declared on the interface but currently never read or forwarded by the card.

onDismiss
(id: string) => void
Optional
—

Renders the dismiss button; invoked with the toast id.

dismissLabel
string
Optional
'Dismiss notification'

aria-label for the dismiss button (EN default — pass a localized string on non-EN pages).

className
string
Optional
—

Additional CSS classes on the card.

Quick use:<Toast />

Accessibility

ARIA rolestatus (alert for error)

Type

1 rule

NASA Rule 5

id forwards to data-toast-id — unique per toast so a container can target dismiss/update by id

ARIA

1 rule

WCAG 4.1.3

error → role='alert' + aria-live='assertive' (interrupts); other variants → role='status' + aria-live='polite'; aria-atomic='true' on the card; dismiss button is aria-labelled via dismissLabel

Usage

Import

tsxSnippet
import { Toast } from "@epasslive/design-system"

Example 1

tsxSnippet
<Toast id="save-ok" variant="success" title="Changes saved" />

Example 2

tsxSnippet
<Toast id="pay-fail" variant="error" title="Payment failed" description="Card declined." action={{ label: "Retry", onClick: retry }} onDismiss={dismiss} />

Anti-patterns

— Do not promise auto-dismiss — the card ships no timer and duration is not yet forwarded

— Do not force every variant into one polite live region — error must stay alert/assertive

Design System v4.1.0 | ePassLive