Toast

A brief report that something already happened. Covers toast and toast-region.

Toast

dismiss is the one prop here that needs Alpine — it takes an expression that removes the toast. Everything else works in a plain Blade view.

Put the region in your layout, once

{{-- resources/views/layouts/app.blade.php --}}
<x-ds::toast-region position="bottom-right" />

Always rendered, and empty most of the time. A live region only announces content that arrives after it is in the document — render the region together with the first toast and the first toast is silent, which is the one a reader most needs to hear.

Then add toasts

<x-ds::toast-region position="bottom-right">
    @foreach ($toasts as $toast)
        <x-ds::toast :tone="$toast->tone" :title="$toast->title" dismiss="…">
            {{ $toast->body }}
        </x-ds::toast>
    @endforeach
</x-ds::toast-region>

toast-region props

PropTypeDefaultWhat it does
positionbottom-right top-right bottom-center top-centerbottom-right
labelstringNotificationsThe region's name in a landmark list.

toast props

PropTypeDefaultWhat it does
toneneutral accent success warning dangerneutralLives in the icon. Resolved on the server — see below.
titlestring—Bold first line.
iconstringtone's default
dismissAlpine expression—Renders a close control that runs this.

Plus an action slot.

A dynamic list of toasts

A toast list is inherently dynamic, which makes this the component where the limitation below bites hardest and is most natural to walk into.

tone resolves to classes on the server. They are chosen when the view renders, so binding it to Alpine state does nothing. See Driving components from client-side state.

{{-- Renders every toast neutral, whatever item.tone says --}}
<template x-for="item in toasts" :key="item.id">
    <x-ds::toast ::tone="item.tone" ::title="item.title" />
</template>

::tone sets a tone attribute on the rendered element. Nothing reads it — the icon and its colour were decided before the browser saw the markup.

If the toasts come from the server, which is the common case, nothing is wrong: render the region in the response, or from a Livewire component, and each toast gets its tone the ordinary way. That is the @foreach at the top of this page.

If the list lives only in the browser, use one <template x-for> per tone over a filtered list:

<x-ds::toast-region position="bottom-right">
    @foreach (['success', 'danger', 'warning', 'neutral'] as $tone)
        <template x-for="item in toasts.filter(t => t.tone === '{{ $tone }}')" :key="item.id">
            <x-ds::toast tone="{{ $tone }}" ::title="item.title"
                dismiss="toasts = toasts.filter(t => t.id !== item.id)" />
        </template>
    @endforeach
</x-ds::toast-region>

That is correct and it has a real cost: toasts group by tone rather than by arrival order. Four successes and one failure show the failure in tone order, not last. If arrival order matters more than tone — and for a failure it usually does — render the region from the server instead.

Why tone is not a data attribute

The obvious fix is to make the toast take data-tone and drive the icon colour from CSS, so a single x-for would work. It has not been done, and the reason is worth writing down rather than rediscovering:

  • The tone does not only pick a colour. It picks the icon — check-circle for success, exclamation-triangle for warning — and an icon is markup that CSS cannot swap. A data-attribute toast would still need a bound :name on the icon, so it would solve half the problem and add a second styling mechanism to the system for it.
  • It would be the only component styled that way. badge, alert and sheet-item have the same shape, and one exception that behaves differently from its three siblings is worse than a limitation all four share.
  • CSS driven from a data attribute is invisible to Tailwind's scanner in exactly the way this system spends most of its effort avoiding.

If a tone-per-item toast becomes the common case rather than the occasional one, the honest answer is a toast-list component that takes an array and renders it server-side — not a second way to style one toast.

Toast or alert

Anything the reader must act on is not a toast. It is on screen for a few seconds, somewhere their eyes are not, and on a phone it may be under a thumb.

A failure that needs a retry belongs in an alert next to the thing that failed.

Timing is yours

The component holds no state and no timer. What to aim for:

  • ~5 seconds for a plain confirmation.
  • Longer, or never, when the toast has an action. "Undo" on a 3-second timer is a button that is gone before the reader finished reading the sentence offering it.
  • Pause on hover and on focus, or someone moving to click Undo watches it disappear under the pointer.

Accessibility

The region is aria-live="polite" — never assertive. A toast reports something that already happened, and interrupting someone to say a thing they just did worked is exactly the rudeness polite exists to avoid.

Don't

  • Don't stack more than about three. Four is a log, and a log belongs on a page.
  • Don't put a form in one. It can vanish mid-typing.
  • Don't use one for a validation error. The field needs the message.
  • Don't put the only copy of an "Undo" behind a timer.

More in specs/toast.md.


From the package repo: edit this page on GitHub.

Was this page helpful?