Zero-dependency notifications that follow your page's theme on their own. Click anything below — every card is live.
Info, success, error, warning — errors announce assertively.
Toast.success('Changes saved')
Toast.error('Could not reach the server',
{ title: 'Save failed' })
One action button; the toast dismisses after the click.
Toast.show('Message deleted', {
action: { label: 'Undo',
onClick: () => restore() }
})
Loading → success or error, in place.
Toast.promise(saveUser(), {
loading: 'Saving…',
success: u => `Saved ${u.name}`,
error: e => `Failed: ${e.message}`
})
Six stacks; newest sits nearest the edge. Hover a stack to pause it.
Toast.defaults.position = 'top-center'
duration: 0 stays until dismissed.
Toast.warning('Reconnecting…',
{ title: 'Connection lost', duration: 0 })
With the VC core loaded, one call themes every component.
VC.config({ accent: '#b45309' })
Toast is a static API — there is no constructor. Every call returns a handle for the toast it created.
Toast.show(message, options?) // → handle { el, dismiss(), update(message, options?) }
Toast.info(message, options?) // type shorthands — same signature,
Toast.success(message, options?) // with `type` preset
Toast.error(message, options?)
Toast.warning(message, options?)
Toast.loading(message, options?) // sticky (duration 0) unless you set one
Toast.promise(promise, msgs, options?) // loading → success | error; returns the promise
Toast.dismissAll()
Messages are text by default (rendered with textContent); pass html: true to opt in to trusted markup. Also works with CommonJS/AMD and is SSR-safe — without a DOM every call is a no-op returning a dummy handle.
Second argument to Toast.show() and the shorthands. The first eight are keys of Toast.defaults — change one there to affect every toast, e.g. Toast.defaults.position = 'top-center'. The rest are per-call only.
| Name | Type | Default | Description |
|---|---|---|---|
position | string | 'bottom-right' | top|bottom + -left|-center|-right — six stacks; unknown values fall back to 'bottom-right'. |
duration | number | 4000 | Milliseconds before auto-dismiss; 0 = sticky until dismissed. |
type | string | 'info' | 'info' | 'success' | 'error' | 'warning' | 'loading' — icon, color, and ARIA role. |
dismissible | boolean | true | Show the ✕ button. |
max | number | 5 | Per-stack cap; the oldest toast is evicted beyond it. |
styles | boolean | true | false = headless, no CSS ever injected (see Theming). |
theme | string | 'auto' | 'auto' | 'light' | 'dark' — resolved page-wide from Toast.defaults.theme (see Theming). |
labels | object | { dismiss: 'Dismiss' } | Accessible name of the ✕ button (set via Toast.defaults.labels). |
title | string | — | Optional bold title above the message. |
action | object | — | One action button: { label, onClick(handle) }. The toast dismisses after the click. |
html | boolean | false | Opt in to render the message as trusted markup (titles are always text). |
onDismiss | function | — | fn(handle) — see Events & callbacks. |
Every Toast.show()/shorthand call returns a handle:
| Member | Returns | Description |
|---|---|---|
el | Element | The toast’s root .vt element (property, null in SSR). |
dismiss() | undefined | Dismiss this toast (animates out, then fires onDismiss). |
update(message, options?) | handle | Replace message and options in place — resets the timer; how Toast.promise turns a loading toast into success/error. No-op after dismissal. |
| Member | Returns | Description |
|---|---|---|
Toast.show(message, options?) | handle | Show a toast. |
Toast.info / .success / .error / .warning(message, options?) | handle | Type shorthands for show(). |
Toast.loading(message, options?) | handle | Spinner toast; sticky (duration: 0) unless a duration is given. |
Toast.promise(promise, msgs, options?) | the promise | Loading toast that updates into success or error. msgs: { loading, success, error } — success/error may be strings or fn(result | reason) => string. Returns the original promise. |
Toast.dismissAll() | undefined | Dismiss every toast in every stack. |
Toast.defaults | object | Global defaults — change any once, e.g. Toast.defaults.duration = 6000. |
Toast.salt | string | false | CSS isolation namespace (default 'vc1'). Set your own token before the first toast, or false to disable. |
Toast.css | string | The full stylesheet, always rendered with the current salt — a starting point for headless styling. |
Toast.version | string | Library version. |
| Callback | Signature | Fires |
|---|---|---|
onDismiss | fn(handle) | When the toast is dismissed — timer, ✕ button, action click, eviction beyond max, or dismissAll(). |
action.onClick | fn(handle) | When the action button is clicked; the toast dismisses right after. |
Toast dispatches no CustomEvents — the callbacks above are the whole event surface.
None — toasts are inherently imperative (they exist only when your code shows one), so Toast has no data-* auto-init. Set global behavior once via Toast.defaults instead.
Auto light/dark with the family’s resolution order: <html data-theme> / data-bs-theme / .dark class → prefers-color-scheme, re-resolved live. Pin it with Toast.defaults.theme = 'dark'. All colors are CSS custom properties on .vt (dark values are defined on .vt-stack[data-theme=dark] .vt):
| Property | Role |
|---|---|
--vt-accent | Info icon, action button. |
--vt-success | Success icon. |
--vt-error | Error icon. |
--vt-warning | Warning icon. |
--vt-bg | Toast background. |
--vt-text | Text color. |
--vt-muted | Secondary text (message under a title, ✕ button). |
--vt-faint | Border and hover fills. |
--vt-shadow | Toast shadow. |
--vt-radius | Corner radius. |
--vt-font | Font stack. |
CSS isolation — stacks render as class="vt-stack vc1" and structural rules ship salted, so host-page design systems can’t override the toasts; the --vt-* variable overrides above keep working (var definitions are deliberately unsalted). Custom token: Toast.salt = 'acme' before the first toast; disable with Toast.salt = false.
Headless — Toast.defaults.styles = false injects no CSS, ever. You keep the full behavior (stacking, timers, pause-on-hover, ARIA) and the stable markup contract (.vt-stack → .vt.vt-success.vt-in → .vt-icon / .vt-body (.vt-title, .vt-msg) / .vt-action / .vt-close; .vt-out while leaving). Start from Toast.css (string) or dist/toast.css. With the VC core loaded, VC.config({ accent: … }) themes toasts and every other family component in one call.
| Key | Action |
|---|---|
Tab / Shift+Tab | Move focus to a toast’s action and ✕ buttons (native buttons in the document order). |
Enter / Space | Activate the focused action or dismiss button. |
Errors and warnings render with role="alert" / aria-live="assertive"; info, success, and loading use role="status" / aria-live="polite". Hovering a stack pauses all of its timers; they resume with the time they had left (minimum 400 ms so nothing vanishes mid-read). :focus-visible outlines on buttons; all animation is disabled under prefers-reduced-motion: reduce.
Set Toast.defaults.styles = false and no CSS is ever injected —
you keep the full behavior (stacking, timers, pause-on-hover, ARIA) and the stable
.vt-* markup contract to style however you like. Both demos below run in an
<iframe>: the styled examples above have already injected the stylesheet
into this page, so a headless toast out here would still match those rules.
The raw browser rendering — a sticky toast is shown on load (unstyled stacks are not
position: fixed, so it simply flows in the document), and the button adds more.
Toast.defaults.styles = false // headless: zero CSS injected
Toast.info('A raw toast', { duration: 0 })
Toast.success('Changes saved', { title: 'All set' })
The same headless calls with the .vt-* hooks mapped to Tailwind utilities via
@apply — including the .vt-in/.vt-out state classes, so the
enter/leave transitions are yours too. The button fetches cdn.tailwindcss.com,
so nothing loads until you ask.