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">×</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="{"title":"Saved","body":"Changes synced.","variant":"success"}">
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="{"body":"Heads up.","variant":"info"}">
Info
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"Saved.","variant":"success"}">
Success
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"Check this.","variant":"warning"}">
Warning
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"title":"Failed","body":"Try again.","variant":"danger"}">
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="{"body":"top-end","position":"top-end"}">
top-end
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"top-start","position":"top-start"}">
top-start
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"top-center","position":"top-center"}">
top-center
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"bottom-end","position":"bottom-end"}">
bottom-end
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"bottom-start","position":"bottom-start"}">
bottom-start
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"bottom-center","position":"bottom-center"}">
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="{"body":"5 second default","variant":"info"}">
Default 5s
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"10 second read","variant":"info","duration":10000}">
10s custom
</button>
<button class="button is-outlined" type="button" data-demo-toast="{"body":"Manual dismiss only","variant":"info","duration":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">×</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
| Name | Role |
|---|---|
.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-containercarriesrole="region",aria-label, andaria-liveat 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"})setsrole="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>witharia-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
| Token | Default | Description |
|---|---|---|
--toast-max-width | 24rem | Maximum width of the toast container |
--toast-enter-y | -0.5rem | Entrance and exit direction for toasts |
Classes
| Class | Description |
|---|---|
.toast-container | component root |
.is-top-end | Top-end corner position (default) |
.is-top-start | Top-start corner position |
.is-top-center | Top-center position |
.is-bottom-end | Bottom-end corner position |
.is-bottom-start | Bottom-start corner position |
.is-bottom-center | Bottom-center position |
CSS Reference: Toast
Individual floating notification item
Source: src/css/toast.css
Tokens
| Token | Default | Description |
|---|---|---|
--toast-bg | var(--color-surface-float) | Background color for the toast |
--toast-color | var(--color-text) | Text color inside the toast |
--toast-border | var(--color-border) | Border color for the toast |
--toast-accent | var(--color-border-strong) | Accent color for the icon and progress bar |
--toast-min-width | 16rem | Minimum width of the toast |
Slots
| Slot | Description |
|---|---|
> svg:first-child | Leading icon -- first-child <svg>. Direct child only so a deeper SVG inside body content doesn't pick up slot styling. |
.toast-content | Content region with title and body text |
.toast-title | Title text inside the content region |
.toast-progress | The progress bar shown when a timer is counting down |
Classes
| Class | Description |
|---|---|
.toast | component root |
.is-leaving | Exit state applied by toast.js before removal |
.is-info | Info color variant for primary-tone toasts |
.is-success | Success color variant |
.is-warning | Warning color variant |
.is-danger | Danger color variant |