Toast

Show transient, self-dismissing notifications from anywhere in your app using a single global container.

Setup

Drop the container once on your page, typically in your base layout. It registers a global Alpine store, listens on the window for a toast event and renders a stack in each corner.

<c-ui.toast.container />
{% cotton ui.toast.container /%}

To raise a toast, dispatch a browser toast CustomEvent whose detail describes the notification. Anything it omits falls back to the container's props.

Ways to Emit

Any code can fire a toast. There are three common ways:

  • Vanilla JS dispatch a window event directly:
    window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', variant: 'success' } }))
  • Alpine use $dispatch inside any component (Alpine events bubble to the window, where the container listens):
    $dispatch('toast', { message: 'Saved', variant: 'success' })
  • HTMX return an HX-Trigger response header (HTMX re-dispatches it as a window event):
    HX-Trigger: {"toast": {"message": "Saved", "variant": "success"}}
<c-ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', variant: 'success' } }))">
    Vanilla JS
</c-ui.button>

<div x-data>
    <c-ui.button x-on:click="$dispatch('toast', { message: 'Saved', variant: 'success' })">
        Alpine $dispatch
    </c-ui.button>
</div>
{% cotton ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', variant: 'success' } }))" %}
    Vanilla JS
{% endcotton %}

<div x-data>
    {% cotton ui.button x-on:click="$dispatch('toast', { message: 'Saved', variant: 'success' })" %}
        Alpine $dispatch
    {% endcotton %}
</div>

Variants

The variant sets the contextual colour and icon: info, success, warning or error. Add an optional title for a bold heading above the message.

<c-ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Heads up, a new version is available.', variant: 'info' } }))">
    Info
</c-ui.button>

<c-ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved successfully', variant: 'success' } }))">
    Success
</c-ui.button>

<c-ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Your trial ends soon.', variant: 'warning' } }))">
    Warning
</c-ui.button>

<c-ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Could not connect to the server.', variant: 'error' } }))">
    Error
</c-ui.button>

<c-ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { title: 'Invitation sent', message: 'Your teammate will receive an email shortly.', variant: 'success' } }))">
    With title
</c-ui.button>
{% cotton ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Heads up, a new version is available.', variant: 'info' } }))" %}
    Info
{% endcotton %}

{% cotton ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved successfully', variant: 'success' } }))" %}
    Success
{% endcotton %}

{% cotton ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Your trial ends soon.', variant: 'warning' } }))" %}
    Warning
{% endcotton %}

{% cotton ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Could not connect to the server.', variant: 'error' } }))" %}
    Error
{% endcotton %}

{% cotton ui.button onclick="window.dispatchEvent(new CustomEvent('toast', { detail: { title: 'Invitation sent', message: 'Your teammate will receive an email shortly.', variant: 'success' } }))" %}
    With title
{% endcotton %}

Appearances

Three visual treatments, set per toast with appearance or as the container default. soft (default) is a tinted panel, solid is a filled colour and outline is a bordered panel on a plain surface.

{# Per toast #}
window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', variant: 'success', appearance: 'solid' } }))

{# Or as the container default for every toast #}
<c-ui.toast.container appearance="solid" />
{# Per toast #}
window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', variant: 'success', appearance: 'solid' } }))

{# Or as the container default for every toast #}
{% cotton ui.toast.container appearance="solid" /%}

Positions

Six anchor points, set per toast with position or as the container default: top-right (default), top-left, top-center, bottom-right, bottom-left and bottom-center. Bottom stacks grow upward so the newest toast is always nearest the edge. Try each below.

{# Per toast #}
window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', position: 'bottom-center' } }))

{# Or as the container default #}
<c-ui.toast.container position="bottom-center" />
{# Per toast #}
window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Saved', position: 'bottom-center' } }))

{# Or as the container default #}
{% cotton ui.toast.container position="bottom-center" /%}

Custom Styling

For one-off styling beyond the built-in appearances, pass a class in the event detail. It is appended to that toast, so any Tailwind utilities you add win over the defaults.

window.dispatchEvent(new CustomEvent('toast', {
    detail: {
        title: 'Custom',
        message: 'Fully restyled toast',
        class: '!bg-zinc-900 !border-zinc-700 !text-white'
    }
}))

From the Server (HTMX)

Return an HX-Trigger response header naming the toast event. HTMX re-dispatches it as a window event with the JSON value as the event detail, which the container catches automatically.

views.py
import json
from django.http import HttpResponse

def delete_item(request, pk):
    # ... delete the item ...
    response = HttpResponse(status=204)
    response["HX-Trigger"] = json.dumps({
        "toast": {"message": "Item deleted", "variant": "error"}
    })
    return response

API Reference

Container Props

Name Description Type Options Default
position Default anchor for toasts that do not specify their own position. str
top-righttop-lefttop-centerbottom-rightbottom-leftbottom-center
top-right
appearance Default visual treatment for toasts that do not specify their own appearance. str
softsolidoutline
soft
duration Default auto-dismiss time in milliseconds. Use 0 for a sticky toast. Overridable per toast. int 4000
class Additional classes merged onto each toast stack wrapper. str

Event Contract

Fire a window CustomEvent named toast. Its detail object accepts:

Name Description Type Options Default
message The toast body text (required for a useful toast). str
variant Contextual colour and icon. str
infosuccesswarningerror
info
appearance Visual treatment for this toast. str
softsolidoutline
container default
position Anchor point for this toast. str
top-righttop-lefttop-centerbottom-rightbottom-leftbottom-center
container default
title Optional bold heading shown above the message. str
duration Auto-dismiss time in milliseconds for this toast. Use 0 to keep it sticky. int container default
class Extra Tailwind classes appended to this toast for one-off styling. str