Skip to content

Toast

hc-toast is a transient notification. You don’t write toast markup — you fire an event (or return an HX-Trigger header) and installToast() renders a .hc-toast into a .hc-toast-region, auto-dismissing it after a delay.

Toasts are rendered by the installToast() behavior — without it, firing an hc:toast event does nothing. Install it once at startup:

import { installToast } from '@hypermedia-components/core';
installToast(); // idempotent; returns an uninstaller

The zero-config @hypermedia-components/core/behaviors entry installs it automatically. No markup is required — the behavior creates a default .hc-toast-region if you don’t pre-render one.

The behavior renders this markup for you — you never write it by hand.

Saved
Your changes were saved.
Profile updated.

Dispatch an hc:toast CustomEvent on document.body:

document.body.dispatchEvent(new CustomEvent('hc:toast', {
bubbles: true,
detail: { message: 'Saved.', title: 'Done', variant: 'success', duration: 4500 },
}));

Try it — each button fires the event. The toast appears in the page corner and auto-dismisses:

…or let the server fire it from a response — htmx turns an HX-Trigger header into the same event:

HX-Trigger: {"hc:toast": {"message": "Saved.", "variant": "success"}}

detail fields: message (required), title?, variant? (info · success · warning · error), duration? ms (0 keeps it until dismissed), id? (see below), action? (see below). error is announced assertively (role="alert"); others use role="status".

Action button. Add action: { label, event } and the toast renders a button; clicking it dispatches a bubbling CustomEvent named event (then dismisses the toast). Catch it with a plain listener or htmx hx-trigger="<event>" on any ancestor — handy for Undo:

document.body.dispatchEvent(new CustomEvent('hc:toast', {
bubbles: true,
detail: { message: 'Item deleted', duration: 0, action: { label: 'Undo', event: 'hc:undo' } },
}));
document.body.addEventListener('hc:undo', () => restoreItem());

Update by id. Give a toast an id; a later hc:toast with the same id updates it in place (re-rendering the message / variant and resetting the auto-dismiss timer) instead of stacking a new one. That models a loading → success / error promise without any client state — the network stays with htmx:

# request fires a sticky loading toast
HX-Trigger: {"hc:toast": {"id": "save-42", "message": "Saving…", "duration": 0}}
# the response updates the same toast
HX-Trigger: {"hc:toast": {"id": "save-42", "message": "Saved!", "variant": "success", "duration": 4500}}

The toast only models the UI states; htmx performs the actual request.

Toasts render into the first [data-hc-toast-region]. If none exists the behavior creates one (bottom-right) for you. Pre-render it to configure position and stacking:

<div class="hc-toast-region" data-hc-toast-region
data-position="top-center" data-limit="3"
role="region" aria-label="Notifications"></div>

data-position anchors the region — {top,bottom}-{left,center,right} (default bottom-right). Top positions stack downward (newest nearest the edge); bottom positions stack upward.

<div class="hc-toast-region" data-hc-toast-region data-position="top-right" ></div>

data-limit="N" caps the number of visible toasts; when a new one arrives the oldest is evicted. Without it the stack is unbounded.

Every toast can be dragged horizontally to dismiss it — past ~40% of its width it flies out, otherwise it snaps back. This is a pointer / touch affordance (keyboard users rely on auto-dismiss); vertical drags still scroll the page. The motion is removed under prefers-reduced-motion.

  • The region is a labelled role="region"; the label is translatable via the i18n catalog (toast.label).
  • Each toast is a live region — role="alert" / aria-live="assertive" for error, role="status" / aria-live="polite" otherwise — so it is announced without moving focus.
  • Don’t rely on color alone: include a title or clear message.
  • Keep duration long enough to read (or 0 for important messages), since swipe-to-dismiss isn’t available to keyboard users.
Token pathPurpose
toast.region-inset / region-gapRegion offset from the edge / gap between toasts.
toast.gapGap between title and body.
toast.min-width / max-widthToast width bounds.
toast.padding-y / padding-x / radiusBox metrics.
toast.{variant}.{bg,fg,border}Per-variant colors.
Show the generated CSS variables
--hc-toast-region-inset | -region-gap
--hc-toast-gap | -min-width | -max-width | -padding-y | -padding-x | -radius
--hc-toast-info-bg | -fg | -border
--hc-toast-success-bg | -fg | -border
--hc-toast-warning-bg | -fg | -border
--hc-toast-error-bg | -fg | -border