Components / feedback
Alert
The close is its own part, and its own decision. data-part="close" is a button with one job, appearing only when onDismiss is passed; action is the slot that may hold an undo. Two controls, two names, and either can be left off.
Summary
A message the reader must see, announced without stealing focus.
| APG pattern | Alert |
| Built on | native elements |
| Status | stable |
| Since | 0.1.0 |
<Alert intent="negative" icon={<Icon path={ICONS.warningCircle} size="md" />} title="Publishing failed">
The domain maya.studio is not verified yet. Point a CNAME at studioedit.app and try again.
</Alert>
Anatomy
A <div data-se="alert"> carrying role="alert" or role="status".
| Part | Element | Required |
|---|---|---|
action | <span> | no |
close | <button> | no |
content | <div> | yes |
icon | <span> | no |
title | <span> | no |
Properties
| Property | Values | Default | What it does |
|---|---|---|---|
intent | accent · positive · negative · warning · neutral | accent | What kind of message this is. It sets the color and the icon slot's tint, and never stands alone — the words carry the meaning. |
live | assertive · polite | assertive | assertive interrupts the reader; polite waits its turn. Default assertive. |
icon | React.ReactNode | none | An <Icon>. Required in practice — color must never carry the meaning alone. |
title | string | none | A short heading above the message. Omit it when the message is one sentence; two lines of heading and body saying the same thing is one too many. |
action | React.ReactNode | none | A dismiss or undo control, pinned to the trailing edge. Omit it and there is none. |
onDismiss | () => void | none | Adds a close button. It is a signal, not a removal: clear the Alert's content and leave it mounted, because unmounting takes the live region with it and the next message is never announced. Omit this and there is no close button. |
dismissLabel | string | Dismiss | The close button's accessible name. Its visible content is a glyph. |
States
| State | Expressed as | |
|---|---|---|
| Focused | :focus-visible | |
| Hover | :hover |
Behavior
It announces when its text changes. It never moves focus — that is the difference between an alert and an AlertDialog.
Accessibility
The element must already be in the DOM before the text changes, or a screen reader announces nothing. Render an empty Alert and fill it; do not mount the component when the message arrives. This is the single most common way live regions silently fail.
assertive interrupts whatever is being read and is the default. Use it when the reader cannot continue without knowing. polite waits for a pause and is right for confirmations.
Never color alone. Always pass an icon and say the problem in words — a red border is a second signal, never the only one.
Keyboard
None of its own. It is not focusable and takes no keys.
Roles, states and properties
| Attribute | Set by |
|---|---|
aria-hidden | always true |
aria-label | set at render |
aria-live | set at render |
role | always alert, always status, set at render |
Focus
It never takes focus, and that is the point. An alert announces without interrupting what the reader is doing. Something that must be dealt with before continuing is an AlertDialog.
Screen reader
Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.
Contrast and target size
npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.
It sets no target size of its own; anything interactive inside it brings its own.
Tested
test/browser/icon-box.spec.mjs
Known gaps
_No upstream requirement is recorded as not applicable._
Appearance
| Token | Used for |
|---|---|
--se-accent | intent |
--se-bg | background, border |
--se-border-width | border |
--se-focus-color | outline |
--se-focus-offset | outline-offset |
--se-focus-width | outline |
--se-icon-md | block-size, font-size, inline-size |
--se-icon-sm | block-size, font-size, inline-size |
--se-leading-lead | line-height |
--se-leading-ui | line-height |
--se-mix-line | border |
--se-mix-soft | background |
--se-negative | intent |
--se-positive | intent |
--se-radius-md | border-radius |
--se-radius-sm | border-radius |
--se-space-1 | gap |
--se-space-3 | gap, padding |
--se-space-4 | padding |
--se-surface-sunk | background |
--se-text | color, intent |
--se-text-md | font-size |
--se-text-muted | color |
--se-text-sm | font-size |
--se-warning | intent |
--se-weight-medium | font-weight |
Motion
None. A message that animates in while a screen reader is already reading it is two things happening at once.
Content
Errors say what happened and how to fix it. The domain is not verified. Point a CNAME at studioedit.app — not Something went wrong.
Name the thing that failed. A reader with three drafts open needs to know which one.
No apology. The message is the fix, not the feeling.
Usage
Page level, section level and field level are three different placements and a message belongs at the narrowest one that can hold it. A field's own error goes in Field; a section's goes in an inline Alert; only something that invalidates the whole page goes above it.
When not to use it. For a confirmation that can be missed — that is a Toast. For something the reader must answer before continuing — that is an AlertDialog, which does take focus.
Notes
action fills data-part="action", and the part was widened to match the prop rather than the prop narrowed to match the part — the type documents it as "a dismiss or undo control", and dismiss named one of the two. This page said the opposite until 0.2, describing a defect that had already been fixed.
The close is its own part, and its own decision. data-part="close" is a button with one job, appearing only when onDismiss is passed; action is the slot that may hold an undo. Two controls, two names, and either can be left off.