Component Porchlight CSS

Toast

Transient notifications with @starting-style enter animation. Auto-stacking via fixed container.

52 components 51 stable 1 experimental

Command

Search Porchlight

Toast

The .c-toast is a transient notification that slides in from the screen edge. Unlike alerts (inline), toasts are overlay-level (z-index 1100) and auto-dismiss via a JavaScript timer. The CSS handles the visual entrance/exit via @starting-style.

Semantic HTML

<div
  class="c-toast-stack"
  data-placement="bottom-end"
  role="region"
  aria-label="Notifications"
>
  <div class="c-toast" data-tone="success" data-visible role="status">
    <span class="c-toast__icon"><svg>...</svg></span>
    <div class="c-toast__content">
      <span class="c-toast__title">Saved</span>
      <span class="c-toast__body">Changes were saved.</span>
    </div>
    <button class="c-toast__close" aria-label="Dismiss">x</button>
  </div>
</div>

Enter/exit

The [data-visible] attribute triggers the enter transition. Removing it triggers the exit. JavaScript should set this attribute when adding a toast, and remove it (then delete the element after the transition) on dismiss.

Timing requirement

@starting-style only fires when a property changes for the first time on an element already in the DOM. If you insert the toast element and add data-visible in the same task/microtask (e.g. inside an Alpine x-init, a React useEffect, or a plain innerHTML assignment followed immediately by setAttribute), the browser batches both operations and @starting-style never sees the “before” state — the toast appears instantly with no animation.

Fix: defer setting data-visible to the next animation frame or microtask after the element is in the DOM:

// Vanilla JS
stack.appendChild(toastEl);
requestAnimationFrame(() => toastEl.setAttribute("data-visible", ""));

// Alpine.js
// x-init="$nextTick(() => $el.setAttribute('data-visible', ''))"

This is not a Porchlight bug — it is how @starting-style works per spec. The deferral ensures the browser registers the initial state (opacity: 0, translated right) before the transition to the visible state.

Stack placement

.c-toast-stack is fixed by default at the block-end / inline-end corner. Use data-placement when an app shell needs another corner:

<div
  class="c-toast-stack"
  data-placement="top-end"
  role="region"
  aria-label="Notifications"
>
  ...
</div>

Supported values:

  • bottom-end (default)
  • bottom-start
  • top-end
  • top-start

Class contract

SelectorRole
.c-toast-stackFixed container (corner placement).
.c-toastIndividual toast notification.
.c-toast__iconLeading icon (tone-colored).
.c-toast__contentTitle + body wrapper.
.c-toast__titleBold heading.
.c-toast__bodySupporting text (muted).
.c-toast__closeDismiss button.
[data-visible]Triggers enter transition.
[data-tone]success, warning, danger (default = accent).
[data-placement]Stack corner: bottom-end, bottom-start, top-end, top-start.