GitHub

Snackbar Container

Bottom-right toast stack. Drive it imperatively — show(config), the variant shorthands, dismiss(id), and clearAll(). Defaults to info / persistent / dismissable, with uniqueKey dedupe. Messages are announced through the shared announcer, not by the stack itself.

Stable wc import '@esa/ecology/esa-snackbar-container'; Angular usage

Preview

Place one container on the page, then call its methods: el.success('Saved.').

<button data-kind="success">Success</button>

<esa-snackbar-container id="toasts"></esa-snackbar-container>
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@esa/ecology/esa-snackbar-container';

@Component({
  selector: 'app-snackbar-container-demo',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <button data-kind="success">Success</button>
    <esa-snackbar-container id="toasts"></esa-snackbar-container>
  `,
})
export class SnackbarContainerDemo {}

API

Methods

PropTypeDefaultDescription
show(config) method — Shows a toast. config: { message, variant?, duration?, action?, dismissable?, uniqueKey? }. Returns its id.
success / info / warning / danger(msg, config?) method — Shorthands for show() with that variant.
dismiss(id) method — Removes the toast with that id.
clearAll() method — Removes every active toast.

Config object (show)

PropTypeDefaultDescription
message string — Toast body text.
variant 'info' | 'success' | 'warning' | 'danger' 'info' Color and leading icon.
duration number 0 Auto-dismiss delay in ms. 0 (the default) keeps the toast until dismissed. This default changed from 5000 on 2026-08-16 — a timer the user cannot adjust is WCAG 2.2.1 Timing Adjustable, a Level A failure. Passing a duration still works and still auto-dismisses; it opts you out of 2.2.1 unless you provide the adjustment yourself.
action string — Optional action button label. Discouraged — see Accessibility below. Only safe when the same action is also reachable in the page itself.
dismissable boolean true Renders the close button.
uniqueKey string — Dedupe key. A second show() with the same key does not stack a second toast, but it DOES re-announce the message and restart any countdown — the second occurrence is a real event.

Attributes

PropTypeDefaultDescription
label string 'Notifications' Accessible name for the region landmark that wraps the stack. Change it only if a page has more than one stack.

Events

EventTypeDefaultDescription
snackbar-action CustomEvent<{ id: string }> — Fired when a toast's action button is pressed.

Accessibility

Toasts are the hardest common pattern to make accessible, and most implementations fail. This one is built persistent-first: the timed, animated version is the enhancement, not the baseline.

The stack is not a live region

Announcing is handled by the shared announcer (@esa/ecology/announcer), which owns the only two live regions in the kit — one polite, one assertive, in the light DOM, mounted before anything happens. Putting aria-live on this stack instead would fail three ways: each toast is created and populated in the same tick, so the region would be born with its content and never announce; the text sits two shadow roots deep, where live-region observation is unreliable; and every page with a stack would own a third region competing with the other two.

What the stack does carry is role="region" with a name, so it is a landmark. An announcement tells someone a toast exists; the landmark is how they get to it.

Timing (SC 2.2.1, Level A)

  • duration now defaults to 0 — the toast stays until dismissed. That is the only configuration that conforms without extra work.
  • If you pass a duration, hovering or focusing the stack pauses every countdown. That helps pointer and keyboard users. It does not help someone reading with a screen reader's virtual cursor, which fires neither event — so pausing is a mitigation, not a conformance argument.
  • Esc dismisses the newest toast, but only while focus is inside the stack. A global handler would let someone closing a tooltip destroy a toast they had not read.

Action buttons

action is supported and discouraged. A live region announces raw text with no roles, so "Undo" is read as a bare word with nothing to say it is a button; focus is never moved to it; and there is no mechanism to navigate to a toast. Add one only when the same action is also available in the page. If the toast is the only route to it, that is not a status message — use esa-dialog, which moves focus and persists.

  • Dismissing a toast that held focus returns focus to wherever it came from, rather than dropping it to <body>.
  • Below 30rem the stack becomes a full-width strip instead of a 420px box overflowing the viewport (SC 1.4.10 Reflow, 1.4.4 Resize).
  • Each toast renders an esa-snackbar-item with a 32px dismiss target and a visible focus ring.
  • The newest toast appears at the bottom of the stack (column-reverse), nearest the user's attention.

Theming surface

Every public token this component reads, extracted from its source at build time, split by blast radius: the hooks that belong to this component, then the shared and system tokens it consumes alongside everything else. The Resolves to column walks each token's real lineage — component → semantic → primitive → raw value — so you can see exactly where re-pointing it lands. A spoke re-skins by overriding these in its theme-<slug>.css; component internals are never edited. No hook for what you need? File it with /request-lego.

Wired to this component

No other component reads these — re-point them and esa-snackbar-container is the only thing that moves.

Token Tier Resolves to
--snackbar-container-max-width component 420px

Shared tokens it reads

Owned by a family or by the semantic layer. Still overridable — but scope the override (esa-foo { --form-border-color: … }) unless you mean to move everything that reads it. Reach says what else that is.

Token Reach Resolves to
--z-toast semantic every component 500
--spacing-200 primitive every component 0.5rem
--spacing-500 primitive every component 1.5rem