Alert Box
Inline status message with a variant-mapped icon, optional title, and optional dismiss control.
Preview
<AlertBox variant="info" title="Heads up">
This is an informational message.
</AlertBox> Variants
<AlertBox variant="info" title="Heads up">This is an informational message.</AlertBox>
<AlertBox variant="success" title="Saved">Your changes were saved successfully.</AlertBox>
<AlertBox variant="warning" title="Check this">Some fields need your attention.</AlertBox>
<AlertBox variant="danger" title="Error">Something went wrong while saving.</AlertBox> States
The title is optional — omit it for a compact single-line alert. With dismissable, a close button appears and a dismissed event bubbles on click.
<AlertBox variant="info">A compact, single-line alert with just a message.</AlertBox>
<AlertBox variant="warning" title="Dismiss me" dismissable={true}>Click the × to hide this alert.</AlertBox> API
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'info' | 'success' | 'warning' | 'danger' | 'info' | Tone — drives the background, border, and auto-mapped icon. |
title | string | — | Optional bold heading above the message. |
dismissable | boolean | false | Shows a close button that hides the alert and emits a dismissed event. Dismissing moves focus to the next focusable element after the box, rather than dropping it to the top of the document. |
live | boolean | false | Announce the box when it is injected into an already-loaded page. Off by default, and deliberately so — see Accessibility below. Ignored (with a console warning) when combined with dismissable, because a live region announces a button as bare text and gives no way to reach it. |
Events
| Event | Type | Default | Description |
|---|---|---|---|
dismissed | CustomEvent | — | Bubbles when the close button is clicked (dismissable only). |
Accessibility
It is not a live region, despite the name
An alert box is the component most likely to be injected at runtime, so making it
role="alert" by default looks obvious. It is wrong for the general case,
for three reasons that compound:
- It takes a slot. Live regions announce raw text with no structure, so a slotted link or button is read as a run-on sentence with no roles. The component cannot see what you put in there.
dismissableputs a control inside it. A live region does not take focus and cannot be navigated to, so the user is told a button exists and given no route to it. A message with a control in it is a dialog — use esa-dialog.- Most alert boxes are present at page load, where there is no change for a live region to report — or worse, where some screen readers read it out on load as noise.
So live is opt-in. Turn it on only when all three hold: the box appears in
response to something the user did, it is not dismissable, and its slot is plain text.
danger then announces assertively (role="alert"); the other
variants wait for a pause (role="status"). Combining live with
dismissable is ignored and warns in the console.
Everything else
- Icons are auto-mapped per variant (info / circle-check / triangle-alert / circle-alert) and marked
aria-hidden. - The dismiss button has an
aria-label="Dismiss". - Dismissing moves focus to the next focusable element after the box. Hiding the box removes the button that had focus, and without this focus falls to
<body>— silently returning a keyboard user to the top of the document.
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.
Type roles it names
This component does not set a size, weight, face or tracking of its own — it names a composite, and the composite carries all five. A composite is a class rather than a token, so none of it appears in the tables below; re-theme text by re-pointing the token family, not by editing the component.
| Role | Re-point | Reach |
|---|---|---|
.typography-body-sm |
| +20 more componentsesa-breadcrumbs, esa-button-toggle, esa-card, esa-checkbox-group, esa-combobox, esa-command-palette, esa-date-picker, esa-empty-state, esa-file-upload, esa-form-field, esa-input-tag, esa-link-column, esa-loading-overlay, esa-progress-bar, esa-radio-group, esa-search-panel, esa-select, esa-stat, esa-text-field, esa-textarea |
.typography-label-sm-strong |
| +2 more componentsesa-collapsible, esa-empty-state |
Wired to this component
No other component reads these — re-point them and esa-alert-box is the
only thing that moves.
| Token | Tier | Resolves to |
|---|---|---|
--alert-box-text-color | component | #646464 via --color-content-default-secondary → --color-gray-11 |
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 |
|---|---|---|
--focus-ring-color | component focus ring group · +34 more | #3e9b4f via --color-border-default-focus → --color-grass-10 |
--focus-ring-offset | component focus ring group · +31 more | 2px |
--focus-ring-width | component focus ring group · +35 more | 2px via --border-width-focus → --border-width-200 |
--border-width-default | semantic every component | 1px via --border-width-100 |
--color-background-overlay-strong-hover | semantic every component | rgba(0, 0, 0, 0.05) via --color-black-a-1 |
--color-background-utility-danger-subtle | semantic every component | #fffcfc via --color-red-1 |
--color-background-utility-info-subtle | semantic every component | #fbfdff via --color-blue-1 |
--color-background-utility-success-subtle | semantic every component | #fbfefc via --color-green-1 |
--color-background-utility-warning-subtle | semantic every component | #fefdfb via --color-yellow-1 |
--color-border-utility-danger | semantic every component | #fdbdbe via --color-red-6 |
--color-border-utility-info | semantic every component | #acd8fc via --color-blue-6 |
--color-border-utility-success | semantic every component | #adddc0 via --color-green-6 |
--color-border-utility-warning | semantic every component | #f3d673 via --color-yellow-6 |
--color-content-default | semantic every component | #202020 via --color-gray-12 |
--color-content-default-secondary | semantic every component | #646464 via --color-gray-11 |
--color-content-utility-danger | semantic every component | #ce2c31 via --color-red-11 |
--color-content-utility-info | semantic every component | #0d74ce via --color-blue-11 |
--color-content-utility-success | semantic every component | #218358 via --color-green-11 |
--color-content-utility-warning | semantic every component | #ab6400 via --color-yellow-11 |
--radius-md | semantic every component | 0.5rem via --radius-200 |
--radius-sm | semantic every component | 0.25rem via --radius-100 |
--transition-fast | semantic every component | 150ms via --duration-150 |
--spacing-050 | primitive every component | 0.125rem |
--spacing-300 | primitive every component | 0.75rem |
--spacing-400 | primitive every component | 1rem |