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:

    variantSemantic roleTypical utilities on .rui-toast--*
    infoinfoborder-info/40, text-info
    successsuccessborder-success/40, text-success
    warningwarningborder-warning/40, text-warning
    errorerror (destructive)border-error/40, text-error
    loadingneutral surfaceborder-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.

    KnobDefaultNotes
    --rui-toaster-width356pxCSS-only; not written by JS
    --rui-toaster-gap14pxgap attribute wins
    --rui-toaster-offset24pxoffset attribute wins
    --rui-toaster-inset-x / --rui-toaster-inset-bottom16pxused 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" with aria-live="assertive" and aria-atomic="true". The stack has no live attribute, so an alert is never nested inside a polite region.
        • Other variants use role="status" with aria-live="off". The toaster announces them through a visually hidden aria-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 same id, as toast.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 to role="alert" is not reliably announced. A new error toast is left to its own alert role.
        • Pass announcement when 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:

        OptionTypeDefaultDescription
        idstring | numberautoDismiss a specific toast by id, or update it in place.
        announcementstringtitle and descriptionPlain text read by the polite live region. Not used for error toasts, which announce themselves.
        descriptionstringSupporting detail under the title.
        durationnumber4000Lifetime in ms; Infinity keeps it until dismissed.
        action{ label, onClick }Render an action button.
        dismissiblebooleantrueAllow close button / swipe-to-dismiss.
        closeButtonbooleantoaster defaultShow the corner close control.
        closeLabelstringClose toastAccessible name of the close control.
        positionToastPositiontoaster defaultPer-toast placement override.

        <rui-toaster> attributes

        AttributeTypeDefaultDescription
        positiontop/bottom-start/center/endbottom-endCorner / edge placement.
        durationnumber4000Default lifetime in ms.
        visible-toastsnumber3Max toasts mounted at once; older wait in queue.
        close-buttonbooleanfalseShow a close button on every toast.
        expandbooleanfalseAlways render the stack expanded.
        gapnumber14Gap between expanded toasts in px.
        offsetnumber24Viewport inset in px.
        containerstring''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.

        AttributeTypeDefaultDescription
        toast-idstring''Toast id for deadline bookkeeping.
        titlestring''Heading text.
        descriptionstring''Supporting detail.
        variantdefault · info · success · warning · error · loadingdefaultStatus tone.
        durationnumber4000Lifetime before auto-dismiss.
        dismissiblebooleantrueAllow close / swipe.
        close-buttonbooleanfalseCorner close control.
        close-labelstringClose toastAccessible name of the close control.
        action-labelstring''Action button text.
        positionToastPositionbottom-endPlacement; mirrors the toaster.
        marked-deletebooleanfalseMarks for animated exit.

        Events

        EventDetailDescription
        rui-toast-showToastShowDetailDocument-level; the toaster listens and creates a toast.
        rui-toast-dismiss{ id? }Document-level; the toaster dismisses the matching toast.
        rui-toast-mountedBubbles 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):

        ClassDescription
        .rui-toaster-regionAnnouncement region wrapper (display: contents).
        .rui-toasterThe <ol> stack list.
        .rui-toastToast surface (role="status"; role="alert" for the error variant).
        .rui-toast--infoInfo tone.
        .rui-toast--successSuccess tone.
        .rui-toast--warningWarning tone.
        .rui-toast--errorError tone.
        .rui-toast--loadingLoading tone.
        .rui-toast__iconStatus icon wrapper.
        .rui-toast__loaderSpinner for loading toasts.
        .rui-toast__contentTitle + description column.
        .rui-toast__titleHeading.
        .rui-toast__descriptionSupporting detail.
        .rui-toast__actionOptional action button.
        .rui-toast__closeCorner close control.

        Theme roles (per variant)

        VariantCSS 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.