Toast
Event-driven notifications that appear temporarily to confirm actions or surface warnings. Toasts auto-dismiss after a configurable duration and stack vertically in a fixed corner of the viewport.
Basic Usage
Mount the toast region once per layout (typically in your app shell):
{{-- In your layout file --}}
<x-wirekit::toast-region />
The region listens on window, so anything that dispatches the event reaches it — a Livewire component after the write succeeds, or Alpine's $dispatch for a purely client-side action.
Most toasts report the outcome of something the server did, so that is the shape shown here: the button calls a Livewire action, and the toast is dispatched after the save returns. Nothing in the markup decides what the toast says.
Variants
Four semantic variants control color, icon, and ARIA behavior. Click each button to see the toast:
Position
Control where toasts appear on the screen:
<x-wirekit::toast-region position="top-right" /> {{-- default --}}
<x-wirekit::toast-region position="top-left" />
<x-wirekit::toast-region position="top-center" />
<x-wirekit::toast-region position="bottom-right" />
<x-wirekit::toast-region position="bottom-left" />
<x-wirekit::toast-region position="bottom-center" />
Each region below uses a scoped name prop so it only listens on its own channel — avoiding cross-talk with other regions on the same page:
Duration
Toasts auto-dismiss after 5 seconds by default. Override per-region or per-toast:
{{-- All toasts in this region dismiss after 3 seconds --}}
<x-wirekit::toast-region :duration="3000" />
// Per-toast override (0 = persistent, must be manually dismissed)
$dispatch('wirekit-toast', {
title: 'Permanent',
message: 'This toast stays until dismissed.',
duration: 0
})
Queue Limit
Limit how many toasts are visible at once. Oldest toasts are removed when the limit is reached:
<x-wirekit::toast-region :max="3" />
Scoped Regions
By default, all toast regions listen on the global wirekit-toast event. When you have multiple regions (e.g. one for system notifications, one for form feedback), use the name prop to scope each region to its own event channel. This prevents duplicate toasts appearing in every region.
{{-- System notifications — listens on 'wirekit-toast-system' --}}
<x-wirekit::toast-region name="system" position="top-right" />
{{-- Form feedback — listens on 'wirekit-toast-form' --}}
<x-wirekit::toast-region name="form" position="bottom-right" />
Dispatch to a specific region by targeting its scoped event name:
// Only appears in the "form" region
$dispatch('wirekit-toast-form', {
variant: 'success',
title: 'Saved',
message: 'Your changes were saved.'
})
// Only appears in the "system" region
$dispatch('wirekit-toast-system', {
variant: 'warning',
title: 'Maintenance',
message: 'Scheduled downtime in 30 minutes.'
})
Regions without a name prop continue to listen on the default wirekit-toast event, so existing code works unchanged.
eventScope — DOM-containment filter (alternative to name)
When you can't (or don't want to) coordinate distinct event names — for example because every region on the page emits the same wirekit-toast event but each region should only handle events from its own portion of the DOM — set eventScope to a CSS selector. The region then ignores any event whose dispatching element doesn't sit inside an ancestor matching the selector:
{{-- Per-section toast surface: only handles events dispatched from inside the wrapper --}}
<div data-wk-section-toast>
<x-wirekit::toast-region eventScope="[data-wk-section-toast]" position="top-right" />
{{-- Buttons in this section dispatch the standard event; only THIS region picks it up --}}
<x-wirekit::button @click="$dispatch('wirekit-toast', { variant: 'success', message: 'Saved' })">
Save
</x-wirekit::button>
</div>
name (event-name routing) and eventScope (DOM-containment filtering) are independent and may be combined — set both to require both a matching event name AND a dispatcher inside the scope wrapper. Default null means no containment filter (every dispatched event of the matching name is handled — the existing global-listener behavior).
This is the cleanest pattern for "per-card local toast queues" and similar layouts where the section that emits the toast is the section that should display it.
Pause on Hover
Auto-dismiss pauses when the user hovers over a toast and resumes when they move away. This is built-in — no configuration needed.
Dispatch Payload
The wirekit-toast event accepts:
| Property | Type | Default | Description |
|---|---|---|---|
title |
string |
null |
Bold heading |
message |
string |
'' |
Body text |
variant |
string |
'info' |
info | success | warning | danger |
duration |
number |
Region default | Auto-dismiss in ms (0 = persistent) |
Width & Layout
Individual toasts have a fixed width of w-80 (20rem / 320px), capped to the viewport width minus 2rem on narrow screens (max-w-[calc(100vw-2rem)]). Position is controlled by the position prop on the toast region — not by CSS classes. Stack direction and spacing are handled automatically.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
position |
string |
'top-right' |
Where toasts appear. One of: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right |
duration |
number |
5000 |
Default auto-dismiss duration in ms |
max |
number |
5 |
Maximum visible toasts (oldest removed when exceeded) |
name |
string |
null |
Scoped name — listens on wirekit-toast-{name} instead of global wirekit-toast |
label |
string |
null |
Accessible name for the region, and the switch that makes it a landmark (role="region"). Without it the region is not a landmark at all — set it only if every region on the page gets its own name |
eventScope |
string |
null |
CSS selector — when set, only events whose dispatching element is inside an ancestor matching the selector are handled (DOM-containment filter, complements name's event-name routing) |
filled |
bool |
false |
Use full variant background color instead of light tint (like a filled callout) |
scope |
string |
null |
Scoped personalization key |
Accessibility
- The announcement is separate from the toast. The region renders two permanently-present visually-hidden live regions — one
aria-live="polite", onearia-live="assertive", botharia-atomic="true"— and a new toast writes its text into one of them. A live region has to exist before the text it carries: an element that arrives already holding its message is a new node, not a region that changed, and assistive technology stays silent. - The toast card itself carries no live role and no
aria-live. That is deliberate rather than missing.role="alert"impliesaria-live="assertive", so putting it back on a card created inside the loop would either announce nothing (best case) or have the message read twice (worst case). The card is ordinary content, reachable and readable like any other. dangergoes to the assertive slot; everything else — includingwarning— goes to the polite one. Assertive interrupts whatever the reader is in the middle of, and a notice that dismisses itself after five seconds rarely earns that. A warning that genuinely must be acknowledged does not belong in a toast at all (see Pitfalls below); it belongs in<x-wirekit::alert-dialog>or an inline<x-wirekit::alert>, where nothing takes it away.- Each toast has a dismiss button labeled
aria-label="Dismiss notification", translated through WireKit's own catalog. - Toasts never steal focus when they appear.
- Decorative icons are
aria-hidden="true". - Pass
labelto expose the whole region as a named landmark (role="region"). Without it the region is not a landmark — an unnamed one is worse than none, and several regions on a page sharing one built-in name is whatlandmark-uniquereports.
Keyboard Interaction
The toast card is not focusable, but the dismiss button inside it is.
| Key | Action |
|---|---|
| Tab | Move to the next dismiss button in the stack |
| Enter / Space | Dismiss the focused toast |
Two behaviors follow from that, and both are what a reader working the stack by keyboard needs:
- Focus pauses the auto-dismiss timer, the same way hovering does — otherwise a toast disappears mid-reach.
- Dismissing a toast hands focus to the next one (or to the previous one at the bottom of the stack, or back to wherever you tabbed in from once the stack is empty). Focus is never dropped to the top of the document.
Pitfalls
- Don't use a toast for errors that block progress. Toasts auto-dismiss; errors that the user must acknowledge belong in
<x-wirekit::alert-dialog>or an inline<x-wirekit::alert>. - Don't show more than 3 toasts simultaneously. Stacking degrades into noise — the component's queue dispatcher already enforces a soft limit, but custom dispatch logic should respect it.
Design Tokens
Toasts use the same tinted-background approach as Alert for visual consistency:
| Token | Usage |
|---|---|
--color-wk-accent |
Info variant icon + border tint |
--color-wk-success |
Success variant |
--color-wk-warning |
Warning variant |
--color-wk-danger |
Danger variant |
--shadow-wk-lg |
Toast card elevation |
--radius-wk-md |
Card corner radius |
--padding-wk-x-md / --padding-wk-y-md |
Card padding |
--font-wk-sans |
Font family |
--transition-wk-duration |
Enter/leave animation timing |
Customization
Override toast styles via personalization:
WireKit::personalize('toast-region', [
'base' => 'fixed z-[9999] flex flex-col gap-4 p-6',
'toast' => 'w-96 rounded-xl shadow-2xl',
]);