registry:ui · NativeHTML
alert — the inline notice
An in-flow notice block for state announcements: the variant ladder is its surface (outline plain, tonal tinted), the live-region role is a prop (status polite by default, alert assertive), and hue injects through the global tokens — never a variant name. Not the modal surface (that is system-dialog) and not the transient toast.
overview
Overview
State news that belongs to the page's flow — a deploy landed, a budget failed, maintenance is scheduled. The notice stays until the caller removes it; the banner itself is the live region.
The banner composes four optional parts around its body copy: an icon inline-start of the title, the title, the body, and a × dismissal control at the title row's inline-end. Title-only and body-only notices are first-class — no part is required.
Two decisions are the author's, as props. The surface — variant: outline,
the transparent ground with the neutral border, or tonal, the 12% tinted emphasis. The
announcement — assertive: role=status announces politely when the reader is
idle; role=alert interrupts immediately. Hardcoding either role would be the guess this
component refuses to make.
The dismissal lifecycle is the third axis: persistent (default), manual (the × fires onDismiss('button')), or auto (a dismissAfter timer, default
6000 ms, fires onDismiss('timer')). The alert only signals — presence stays
the caller's ({#if} unmount), and teardown cancels the timer.
Hue is injection: the tonal slot reads --jx-tonal (the brand tint by
default) and utilities like jx-hue-error retarget it —
failed statuses inject --error; the destructive hue is an action hue, never
a status. The eight universal style axes resolve on the same banner — documented per-axis
below; the shared grammar lives on the universal props page.
usage
Usage
The minimal working example, live: the variant pair across both live-region roles, plus the dark bridge on one notice. The icon snippet composes inline-start of the title — bring your own glyph; the drawer shows the exact registry copy this page runs.
alert
Deployed
Scheduled maintenance
Build failed
dark profile
<script lang="ts">
import Alert from '@ui/alert.svelte';
</script>
<Alert title="Deployed">
Build 4f2a shipped to production — 0 checks failed.
</Alert>
<Alert variant="tonal" title="Scheduled maintenance">
The registry pauses syncs Sunday 02:00–02:30 UTC.
</Alert>
<Alert variant="tonal" class="jx-hue-error" assertive={true} title="Build failed">
Exit 1 — the bundle exceeded the size budget by 12 KB.
</Alert>
<Alert variant="tonal" title="dark profile" theme="dark">theme="dark" stamps the .dark class bridge on this banner only — a partial re-theme: the tonal pair re-scopes to the island's dark profile; the semantic ink layer stays root-anchored (W-next #1).</Alert>props
Props
Ten props: variant sets the ladder surface, assertive swaps the live-region role, and the dismiss axis owns the lifecycle — presence stays the caller's. The eight universal axes ride the same banner (their per-axis story is the next section).
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| variant | 'outline' | 'tonal' | 'outline' · ambient zone | Ladder prominence: outline paints a transparent ground with the --jx-outline border; tonal paints the 12% tinted ground with the tonal title ink. Hue comes from token injection, not variant names. Omitted → the ambient paint zone (ButtonGroup / zone scope), else the frozen own. |
| assertive | boolean | false | true → role=alert (immediate); false → role=status (polite). |
| title | string | — | One-line heading; omitted renders a bare body block. |
| icon | Snippet | — | Rendered inline-start of the title — bring your own glyph (sized 1em by the family css). |
| dismiss | 'manual' | 'auto' | — (persistent) | The lifecycle axis, orthogonal to variant and assertive: undefined = persistent (no button, no timer); 'manual' = a × button at the title row's inline-end firing onDismiss('button'); 'auto' = manual + a mount-armed timer firing onDismiss('timer'). The alert only signals — presence is the caller's ({#if} unmount). The button rides inside the live region (the toast-viewport family precedent). |
| dismissAfter | number | 6000 | 'auto' duration in ms; a change re-arms the timer. A same-instance message swap does NOT reset the clock — key the alert ({#key notice}) when each message deserves a fresh deadline. Pairing 'auto' with assertive (immediate announcement that evaporates in 6s) reads aggressive to screen-reader users; polite (the default) is the companion. |
| onDismiss | (how: 'button' | 'timer') => void | — | The dismissal signal: how='button' (× click) or how='timer' (auto deadline). Teardown cancels the timer — an unmounted alert never fires. |
| dismissLabel | string | 'dismiss' | The × button's aria-label. |
| children | Snippet | — | Body copy; omit for a title-only notice. |
| class | string | '' | Forwarded to the banner element; intent utilities like jx-hue-error (or arbitrary token injections outside the closed set) land here. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |
axes
The eight axes on alert
Alert resolves all eight universal axes through its family Defaults (AlertDefaults) and stamps the resolved carriers on the banner root. What each axis drives on THIS family differs: theme partially re-themes the banner (the tonal pair re-scopes under the .dark island; the semantic ink layer stays root-anchored), density moves the × hit lane, radius supplies the concentric anchor for nested parts — and four axes stamp-and-supply without touching the banner's own paint. The lane grammar (named · auto · number · query()) is the universal props page's.
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | Stamps --jx-size-effective and the root font-size on the banner — the ambient reference for em-sized content inside the notice. The banner's own type voices are rem-anchored (the fixed 13px --jx-text-base rhythm) and do not scale. Steps resolve var(--jx-size-*) — 14 / 16 / 18px. |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' | Stamps --jx-shape-effective and --jx-radius-factor-effective, supplied downward. The banner's own corners never read the pair — supply-only on this family. The degrade table is the universal props page's. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | Stamps --jx-radius-effective — the concentric anchor: a nested part at radius="auto" computes max(0px, R − inset) off it (the demo below resolves 6px from 20). The banner's own corners stay the theme's --radius (0px; 8px where corner-shape is supported). |
| density | 'small' | 'medium' | 'large' | 'auto' | number (+ the five legacy spellings) | 'auto' | A named rung stamps the data-density scope (small/medium/large alias sm/default/lg; the five legacy spellings keep working); a number stamps --jx-density-coefficient — inert on this banner: nothing here scales by the coefficient, and only the named rungs move --jx-hit, the × affordance's hit lane (a bare number declares no scope block, so nothing re-declares AT the banner). The banner's type and padding rhythm are static tokens — var(--jx-text-base), var(--space-12), calc(var(--jx-unit) * 3.5) — and none of them reads a density channel. The declaration-only density posture retired at explicit-props D5 (migration-census.md, W3 CLOSE). |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | Stamps --jx-color-effective, supplied downward. The banner's own hue seam is the tonal token pair (--jx-tonal / --jx-outline) retargeted by the jx-hue-* utilities — inject hue through class (jx-hue-error), not this prop. A number is hue degrees through the oklch formula; a raw string passes through verbatim. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | A PARTIAL re-theme on this banner, measured (the drift ledger's W-next #1): dark stamps the .dark class bridge on the root, and the variant-grammar slots re-scope — the sheet declares --jx-tonal and --jx-outline on every theme scope (the canvas-bug law), so the tonal ground, border and its title/body inks follow the island's dark profile. The stylex semantic ink layer does NOT follow: --jx-foreground, --jx-muted-foreground, --jx-ring and the shadow ink stay :root-anchored — the outline rung's ramp, the × affordance and its focus ring keep the light values inside a dark island (documented supply-side until the protocol pass). system resolves the JS-mutable global; auto inherits the tree. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | Stamps --jx-elevation-effective (level-1…level5 are exact dp; a number snaps down to the enclosing rung). The banner's shadow is the fixed hard-offset shadow-2xs — the site's terminal material law — so the axis is supply-only here. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | Stamps --jx-motion-effective (a coefficient; named steps resolve var(--jx-motion-*)). The banner ships no motion kernels — nothing to scale — supply-only here. |
alert · on the axes
concentric anchor
large rung
2xs rung
<script lang="ts">
import Alert from '@ui/alert';
import Card from '@ui/card';
</script>
<div class={cx(rt.col16, rt.wFull)}>
<!-- radius: the banner SUPPLIES the concentric anchor; size stamps the root -->
<Alert variant="tonal" title="concentric anchor" radius={20} size={18}>
<Card radius="auto">child at auto — corners resolve max(0px, 20 − 14) = 6px off the banner's anchor</Card>
</Alert>
<!-- density: the named rung moves the × hit lane -->
<div class={cx(rt.gridSm2)}>
<div class={cx(rt.panel)}><Alert title="large rung" density="lg" dismiss="manual">The × affordance rides the lg hit lane.</Alert></div>
<div class={cx(rt.panel)}><Alert title="2xs rung" density="2xs" dismiss="manual">The × affordance rides the 2xs hit lane.</Alert></div>
</div>
</div>alert · query()
Responsive dismiss target
<script lang="ts">
import Alert from '@ui/alert.svelte';
import { query } from '@lib/universal-props-query.svelte';
import type { DensityLane } from '@lib/defaults.svelte';
</script>
<Alert title="Responsive dismiss target" dismiss="manual" density={query<{ sm: DensityLane }, DensityLane>({ sm: 'small' }, 'large')}>
Media keys are min-width: below 40rem the base applies — the large
rung and its 48px × lane (touch); at 40rem and wider the sm case
wins — the compact small rung (pointer). Resize the window.
</Alert>The supply-only rows are documented absences, not gaps: the banner's corners read the
theme's --radius, its shadow is the fixed hard-offset --jx-shadow-2xs, its hue seam is --jx-tonal, and it ships no
motion kernels — so shape, color, elevation, and motion stamp their carriers and feed
nested consumers without repainting this banner. The fleet classification and the
density-posture retirement are recorded in explicit-props' migration-census.md (W3 CLOSE, LANDED D5).
| Token | Default | Source |
|---|---|---|
| --jx-tonal | var(--primary) | color |
| --jx-outline | var(--border) | color |
| --jx-hit | 24 / 28 / 32 / 40 / 48px (2xs → lg) | density |
| border | 1px | structural |
| shadow | shadow-2xs (hard offset) | structural |
| title rhythm | 13px, fixed utility | structural |
a11y
Accessibility
The banner is itself the live region — screen readers announce it when it appears, and it never takes focus. The × is the one focusable control: a real button inside the live region (the toast-viewport family precedent), labeled by dismissLabel (default dismiss), with a 1px --jx-ring focus-visible outline and the --jx-hit minimum target (28px floor; 24px at the 2xs scope, the WCAG 2.5.8 AA note).
Keyboard
| Key | Action |
|---|---|
| — | Not focusable — an inline live region, not a control |
| × (dismiss) | The one focusable control when dismiss is set — a real button inside the live region (the toast-viewport family precedent), aria-label dismissLabel (default dismiss) |
ARIA
| Attribute | Value | Description |
|---|---|---|
| role | status | alert | status announces politely (default); alert interrupts immediately (assertive). |
| data-jx-alert | variant | Hook attribute carrying the ladder variant (outline | tonal) for styling. |
see also
See also
The surfaces around the notice.