Toast

Ephemeral notification

Intent

Toast is for short-lived, non-blocking notifications -- save confirmations, copy feedback, async errors, server pushes. A positioned .toast-container holds the live region; individual .toast items appear, auto-dismiss after a duration, and animate out. The component supports three usage paths from the same underlying contract: programmatic JS, server-sent HTML (datastar SSE, htmx swap, Turbo), or manual DOM construction. For inline persistent messages, use alert; for modal interruptions, use dialog.

Setup

Two pieces of authored markup, each placed once per page. The container is a positioned region that holds the aria-live landmark -- pre-author it so assistive tech subscribes at page load (dynamically inserted live regions don't reliably announce). Add one container per corner you intend to fire toasts at, picking an is-{position} class like is-top-end or is-bottom-center (see src/css/toast.css for the full set).

<div class="toast-container is-top-end"
     role="region" aria-label="Notifications"
     aria-live="polite"></div>

The template is the cloning source for brio.toast.show() -- only required if you use the programmatic JS path. The slots flagged with data-toast-title, data-toast-body, and data-toast-progress are the contract; the rest of the markup is yours to customize (add an icon, drop the progress bar, change the layout). Server-sent and manual paths skip the template entirely and send finished toast markup straight into the container.

<template id="brio-toast">
    <output class="toast" role="status">
        <div class="toast-content">
            <div class="toast-title" data-toast-title hidden></div>
            <p data-toast-body></p>
        </div>
        <button class="dismiss" type="button" aria-label="Dismiss">&times;</button>
        <div class="toast-progress" data-toast-progress hidden></div>
    </output>
</template>

Programmatic usage

Call brio.toast.show() with a body and any optional title, variant, duration, or position. Returns the live toast element for further control.

<button class="button" type="button" data-demo-toast="{&#34;title&#34;:&#34;Saved&#34;,&#34;body&#34;:&#34;Changes synced.&#34;,&#34;variant&#34;:&#34;success&#34;}">
    Show success toast
</button>
brio.toast.show({
    title: "Saved",
    body: "Changes synced.",
    variant: "success",
});

Variants

Standard four-color status family, mirroring alert. is-info maps to the primary palette, is-success, is-warning, and is-danger to their respective semantic colors. The is-danger variant additionally takes role="alert" at the per-toast level so the polite container still announces it assertively.

<div class="cluster">
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;Heads up.&#34;,&#34;variant&#34;:&#34;info&#34;}">
        Info
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;Saved.&#34;,&#34;variant&#34;:&#34;success&#34;}">
        Success
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;Check this.&#34;,&#34;variant&#34;:&#34;warning&#34;}">
        Warning
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;title&#34;:&#34;Failed&#34;,&#34;body&#34;:&#34;Try again.&#34;,&#34;variant&#34;:&#34;danger&#34;}">
        Danger
    </button>
</div>

With an icon

The default template ships without an icon. To add one, drop an <svg aria-hidden="true"> as the first child of the toast root in your own template (or server-rendered markup). The CSS picks up first-child <svg>, sizes it, and tints it with the variant's --toast-accent color.

The button below clones a hidden <template> that includes a checkmark SVG, simulating the same shape a server response or custom template would emit.

<button class="button" type="button" data-demo-toast-clone="demo-toast-icon">
    Show toast with icon
</button>

Positions

Each position needs its own .toast-container on the page (this docs page authors all six). Pass position to show() to pick where the toast appears.

<div class="cluster">
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;top-end&#34;,&#34;position&#34;:&#34;top-end&#34;}">
        top-end
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;top-start&#34;,&#34;position&#34;:&#34;top-start&#34;}">
        top-start
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;top-center&#34;,&#34;position&#34;:&#34;top-center&#34;}">
        top-center
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;bottom-end&#34;,&#34;position&#34;:&#34;bottom-end&#34;}">
        bottom-end
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;bottom-start&#34;,&#34;position&#34;:&#34;bottom-start&#34;}">
        bottom-start
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;bottom-center&#34;,&#34;position&#34;:&#34;bottom-center&#34;}">
        bottom-center
    </button>
</div>

Duration

Toasts auto-dismiss after the duration passed in show(), defaulting to 5000ms for info / success / warning. The is-danger variant defaults to 0 -- sticky -- because errors deserve user acknowledgment. Pass 0 explicitly on any variant to make it sticky. Hovering or focusing inside the container pauses both the JS timer and the progress bar (WCAG 2.2.1).

<div class="cluster">
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;5 second default&#34;,&#34;variant&#34;:&#34;info&#34;}">
        Default 5s
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;10 second read&#34;,&#34;variant&#34;:&#34;info&#34;,&#34;duration&#34;:10000}">
        10s custom
    </button>
    <button class="button is-outlined" type="button" data-demo-toast="{&#34;body&#34;:&#34;Manual dismiss only&#34;,&#34;variant&#34;:&#34;info&#34;,&#34;duration&#34;:0}">
        Sticky (0)
    </button>
</div>

Server-sent toasts

For hypermedia stacks, the server returns the finished toast markup and your framework swaps it into a container. brio's MutationObserver detects the insertion and orchestrates lifecycle -- no JS call site on the consumer. Use the data-duration attribute to control the auto-dismiss timer; absent means the variant-aware default applies (5000ms for most, 0 for danger).

<output class="toast is-success" data-duration="5000">
    <div class="toast-content">
        <div class="toast-title">Saved</div>
        <p>Returned by the server.</p>
    </div>
    <button class="dismiss" type="button" aria-label="Dismiss">&times;</button>
    <div class="toast-progress"></div>
</output>

The button below simulates the swap by cloning a hidden <template> on this page into the .toast-container.is-top-end. In production, a datastar SSE event, htmx hx-swap, or Turbo Stream insertion would do the same thing.

<button class="button" type="button" data-demo-toast-clone="demo-toast-server-sent">
    Simulate server insert
</button>

Manual adopt()

When you build the toast element yourself (framework component, custom layout, anything outside the standard template), call brio.toast.adopt() to set duration and route it to the right container. The observer takes over from there. This is mostly a convenience over container.appendChild(); both paths reach the same observer.

const el = renderMyToast();        // your DOM building, your way
brio.toast.adopt(el, {
    position: "bottom-end",
    duration: 8000,
});

Reference

NameRole
.toast-container Positioned region holding the live-region landmark. One per corner you use; combine with an is-{position} class.
.toast Individual notification item. Owns the variant tokens, entrance / exit transition, and pointer-events: auto (the container is pointer-events: none so empty corners don't block clicks).
<svg> (first child of .toast) Optional leading icon. Picks up --toast-accent; add aria-hidden="true". Slot keyed by element.
.toast-content Wrapper for title + body. Flex grows to fill between icon and dismiss / meta slots.
.toast-title Optional bold heading line. data-toast-title in the template flags it as a slot.
<p> (inside .toast-content) Body copy. data-toast-body flags the JS slot (used by brio.toast.show()); the styling targets the element. Slot keyed by element.
.dismiss Close button. brio's existing dismiss primitive; toast.js's click delegation handles the timer-aware close.
.toast-progress Optional auto-dismiss progress bar at the bottom edge. data-toast-progress flags it as a slot; toast.js sets animation-duration when the toast is orchestrated and pauses on container :hover / :focus-within.

Customization

Toast's scoped tokens cover bg, text, border, and accent colors per variant, plus min and max width. The variants remap --toast-bg, --toast-color, --toast-border, and --toast-accent via the semantic palette tokens (--color-success-subtle, --color-success-text, etc.); override these directly on a single .toast for a one-off look, or in a parent scope to retone all toasts. --toast-min-width and --toast-max-width bound the size; --toast-enter-y sets the entrance / exit direction (the position classes flip its sign for bottom-anchored containers). Full token list and defaults: src/css/toast.css.

Accessibility

  • Live regions are pre-authored. .toast-container carries role="region", aria-label, and aria-live at page load so assistive tech can subscribe before any toast appears. Inserting a live region dynamically is unreliable across screen readers.
  • Politeness per message. The container is aria-live="polite" by default; brio.toast.show({variant:"danger"}) sets role="alert" on the toast, overriding the container's politeness for that message (more-assertive role wins per ARIA).
  • Pause on hover and focus. Pointing at or focusing inside a container pauses the JS auto-dismiss timer and the progress animation (WCAG 2.2.1, Timing Adjustable).
  • Native dismissible button. The dismiss button is a real <button> with aria-label="Dismiss" -- tab accessible, Enter / Space activate, no extra wiring.
  • Reduced motion. The CSS fences off the entrance / exit transition and replaces the countdown progress with a static remainder bar at reduced opacity under prefers-reduced-motion: reduce.

CSS Reference: Toast Container

Positioned region that holds toast notifications

Source: src/css/toast.css

Tokens

TokenDefaultDescription
--toast-max-width24remMaximum width of the toast container
--toast-enter-y-0.5remEntrance and exit direction for toasts

Classes

ClassDescription
.toast-containercomponent root
.is-top-endTop-end corner position (default)
.is-top-startTop-start corner position
.is-top-centerTop-center position
.is-bottom-endBottom-end corner position
.is-bottom-startBottom-start corner position
.is-bottom-centerBottom-center position

CSS Reference: Toast

Individual floating notification item

Source: src/css/toast.css

Tokens

TokenDefaultDescription
--toast-bgvar(--color-surface-float)Background color for the toast
--toast-colorvar(--color-text)Text color inside the toast
--toast-bordervar(--color-border)Border color for the toast
--toast-accentvar(--color-border-strong)Accent color for the icon and progress bar
--toast-min-width16remMinimum width of the toast

Slots

SlotDescription
> svg:first-childLeading icon -- first-child <svg>. Direct child only so a deeper SVG inside body content doesn't pick up slot styling.
.toast-contentContent region with title and body text
.toast-titleTitle text inside the content region
.toast-progressThe progress bar shown when a timer is counting down

Classes

ClassDescription
.toastcomponent root
.is-leavingExit state applied by toast.js before removal
.is-infoInfo color variant for primary-tone toasts
.is-successSuccess color variant
.is-warningWarning color variant
.is-dangerDanger color variant