Toast
Toasts surface brief, non-blocking feedback after an action, such as save confirmations, errors, or background sync status.
Try it
Usage
Mount RuiToaster once at the app root, then call toast() imperatively to show messages. Each call returns a toast id you can pass to toast.dismiss().
import { RuiToaster, toast } from '@ecopages/radiant-ui/toast';
import { RuiButton } from '@ecopages/radiant-ui/button';
<RuiToaster position="bottom-end" duration={4000} />
<RuiButton on:click={() => toast.success('Changes saved')}>
Save
</RuiButton>The imperative toast() helper is Sonner-shaped: toast(), toast.message(), toast.success(), toast.error(), toast.info(), toast.warning(), toast.loading(), toast.promise(), toast.dismiss(id?), and toast.clear().
Match variant to message
Use success for confirmations, error for failures, warning for caution, and info for neutral updates. loading shows a spinner and never auto-dismisses.
toast.success('Changes saved');
toast.error('Unable to reach the server', { description: 'Try again in a moment.' });
toast.warning('Disk space is running low');
toast.info('Your session will expire soon');Theming
Toast variants reuse the semantic status roles from the active theme (the same info / success / warning / error families as alerts) via .rui-toast--* on the composed surface:
variant | Semantic role | Typical utilities on .rui-toast--* |
|---|---|---|
info | info | border-info/40, text-info |
success | success | border-success/40, text-success |
warning | warning | border-warning/40, text-warning |
error | error (destructive) | border-error/40, text-error |
loading | neutral surface | border-border, text-on-surface |
The surface stays bg-surface text-on-surface with a per-variant border and text accent, so toasts read as floating chrome rather than filled status blocks. Set --info, --success, --warning, or --error in your app's theme stylesheet to change those accents across the theme.
Toaster geometry uses --rui-toaster-* on rui-toaster. gap and offset attributes write --rui-toaster-gap and --rui-toaster-offset and win over CSS.
| Knob | Default | Notes |
|---|---|---|
--rui-toaster-width | 356px | CSS-only; not written by JS |
--rui-toaster-gap | 14px | gap attribute wins |
--rui-toaster-offset | 24px | offset attribute wins |
--rui-toaster-inset-x / --rui-toaster-inset-bottom | 16px | used below 600px |
rui-toaster {
--rui-toaster-width: 28rem;
}Duration and dismissal
Set duration per toast for urgent vs routine messages. Toasts pause their countdown while hovered, while a pointer is interacting, or while the tab is hidden, and resume from the remaining time.
toast('Autosaved', { duration: 2000 });
toast.error('Sync failed', { duration: 10_000, closeButton: true });Enable closeButton when users need time to read, or dismissible to allow swipe-to-dismiss. toast.promise() drives a single toast through loading → success/error:
toast.promise(save(), {
loading: 'Saving…',
success: 'Saved',
error: 'Save failed',
});Stacking
Toasts stack at the configured corner. By default the stack collapses behind the front toast (peek) and expands on hover; set expand to always show full gaps.
<RuiToaster expand position="bottom-end" />visibleToasts caps how many stay mounted at once; older toasts wait in the queue.
Position
Place the stack at any corner or edge center.
<RuiToaster position="top-center" />Accessibility
- Error toasts use
role="alert"witharia-live="assertive"andaria-atomic="true". The stack has no live attribute, so an alert is never nested inside a polite region. - Other variants use
role="status"witharia-live="off". The toaster announces them through a visually hiddenaria-live="polite"region that sits beside the stack. It clears the region and sets the new text about 100 ms later, so a repeated message ("Saved", "Saved") and a toast updated in place (the sameid, astoast.promise()does) announce again, and toasts shown within that gap are announced together. - A toast already on screen that turns into an error (a rejected
toast.promise()) is announced from a second,aria-live="assertive"region beside the stack, because switching an existing toast torole="alert"is not reliably announced. A new error toast is left to its own alert role. - Pass
announcementwhen the title does not read well on its own; it replaces the title and description in the live region. - Screen-reader behavior has not been verified yet with NVDA and Firefox, JAWS and Chrome, or VoiceOver and Safari.
- Do not rely on toasts alone for critical errors; pair with inline field errors when appropriate.
- The stack region is focusable (
tabindex="-1") so keyboard users can inspect messages; close controls are labeled buttons.
Why a custom element?
<rui-toast> owns dismiss lifetime bookkeeping (pause on hover / hidden tab, swipe-to-dismiss, and exit animation) which requires DOM state. <rui-toaster> owns the stack layout (peek/expand math, per-position filtering). RuiToaster is the <rui-toaster> custom-element class; RuiToasterElement is an alias. Mount it once at the app root, then drive it with toast(). RuiToast remains a JSX helper because it converts numeric toastId values to strings; use RuiToastElement when you need the <rui-toast> class.
API
Imperative API
The toast() callable and its variants accept (title, options?) where options is ToastOptions:
| Option | Type | Default | Description |
|---|---|---|---|
id | string | number | auto | Dismiss a specific toast by id, or update it in place. |
announcement | string | title and description | Plain text read by the polite live region. Not used for error toasts, which announce themselves. |
description | string | Supporting detail under the title. | |
duration | number | 4000 | Lifetime in ms; Infinity keeps it until dismissed. |
action | { label, onClick } | Render an action button. | |
dismissible | boolean | true | Allow close button / swipe-to-dismiss. |
closeButton | boolean | toaster default | Show the corner close control. |
closeLabel | string | Close toast | Accessible name of the close control. |
position | ToastPosition | toaster default | Per-toast placement override. |
<rui-toaster> attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
position | top/bottom-start/center/end | bottom-end | Corner / edge placement. |
duration | number | 4000 | Default lifetime in ms. |
visible-toasts | number | 3 | Max toasts mounted at once; older wait in queue. |
close-button | boolean | false | Show a close button on every toast. |
expand | boolean | false | Always render the stack expanded. |
gap | number | 14 | Gap between expanded toasts in px. |
offset | number | 24 | Viewport inset in px. |
container | string | '' | CSS selector for a positioning root (docs canvas). |
Derived tree
<rui-toaster> and <rui-toast> are Derived Tree hosts: the toaster render()s [data-ref="list"] and rui-toast children; each toast render()s [data-ref="toast"], [data-toast-close], and [data-toast-action]. Use the imperative toast() API — do not author these targets by hand. RuiToaster and RuiToast children are a type error.
The toaster queries [data-ref="list"] and [data-ref="toast"] for layout math.
<rui-toast> attributes
Rendered by the toaster; listed for completeness. Documented via @attr on RuiToastElement.
| Attribute | Type | Default | Description |
|---|---|---|---|
toast-id | string | '' | Toast id for deadline bookkeeping. |
title | string | '' | Heading text. |
description | string | '' | Supporting detail. |
variant | default · info · success · warning · error · loading | default | Status tone. |
duration | number | 4000 | Lifetime before auto-dismiss. |
dismissible | boolean | true | Allow close / swipe. |
close-button | boolean | false | Corner close control. |
close-label | string | Close toast | Accessible name of the close control. |
action-label | string | '' | Action button text. |
position | ToastPosition | bottom-end | Placement; mirrors the toaster. |
marked-delete | boolean | false | Marks for animated exit. |
Events
| Event | Detail | Description |
|---|---|---|
rui-toast-show | ToastShowDetail | Document-level; the toaster listens and creates a toast. |
rui-toast-dismiss | { id? } | Document-level; the toaster dismisses the matching toast. |
rui-toast-mounted | Bubbles after a toast mounts and paints; the toaster resyncs stack layout. |
Use showToast() / dismissToast() to dispatch the document events, or the simpler toast() API.
CSS classes
Public BEM classes on the composed light-DOM surface (documented via @cssclass on the elements):
| Class | Description |
|---|---|
.rui-toaster-region | Announcement region wrapper (display: contents). |
.rui-toaster | The <ol> stack list. |
.rui-toast | Toast surface (role="status"; role="alert" for the error variant). |
.rui-toast--info | Info tone. |
.rui-toast--success | Success tone. |
.rui-toast--warning | Warning tone. |
.rui-toast--error | Error tone. |
.rui-toast--loading | Loading tone. |
.rui-toast__icon | Status icon wrapper. |
.rui-toast__loader | Spinner for loading toasts. |
.rui-toast__content | Title + description column. |
.rui-toast__title | Heading. |
.rui-toast__description | Supporting detail. |
.rui-toast__action | Optional action button. |
.rui-toast__close | Corner close control. |
Theme roles (per variant)
| Variant | CSS variables consumed |
|---|---|
info | --info |
success | --success |
warning | --warning |
error | --error |
loading | --border, --on-surface |
error is the destructive status role (same family as destructive buttons). Surface and text roles are --surface / --on-surface for all variants.