DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
AlertBannerToastEmptyStateTooltipCoachMark
AI agentsContributing
  1. Components
  2. Alert

Alert

Inline contextual feedback for the page the user is on — errors, warnings, confirmations and guidance that live in the content flow. Five intents with derived live-region roles, four variants, link-capable actions in two placements, free-form body content, and a parity skeleton.

$ pnpm add @tessinaui/ui

When to use

  • State of this page or section. A failure, a pending step or a piece of guidance that belongs next to the content it describes and stays until it is resolved or dismissed.
  • A form-level summary after submit. Pair it with per-field errors: use titleAs="h2" and autoFocus (Error summary).
  • Persistent success that is delayed or carries a next step, such as a finished import with a "View results" action.
  • Warnings that need attention before they become errors: intent="warning" announces assertively, info and success politely.
  • Short, plain copy: a title that names the issue, one or two sentences, and an action that says what happens next. Meaning never rides on colour alone.

When not to use

  • For a transient confirmation of an action just taken. Use Toast, which clears itself.
  • For site-wide or promotional messaging. Use Banner.
  • For a decision that must interrupt. Use AlertDialog, which traps focus; an alert is never modal.
  • For a single field's error. Use Field errors; the alert is only the form-level summary.
  • More than one at a time. Several alerts stack into noise and get ignored; combine the messages into one.
  • For an error the user must fix, with a dismiss button. Leave onClose off anything that has to be resolved.

Pick the surface by lifetime and blast radius, not by colour:

SurfaceUse forLifetime
ToastTransient confirmation of an action just takenSeconds, auto-dismisses
AlertState of THIS page or section — failures, pending steps, guidanceUntil resolved or dismissed
BannerSite-wide or promotional messaging (upsell, announcements)Page-level chrome
AlertDialogBlocking decisions that must interruptUntil answered

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { Alert } from "@tessinaui/ui";

<Alert
  intent="error"
  title="Something went wrong"
  description="There was an issue processing your request. Please try again."
  action={{ label: "Retry", onClick: retry }}
/>

Examples

Default

A basic alert with a title and supporting description.

Intents

Five semantic intents — none, error, warning, success, and info — each with its own colour, icon and derived live-region role.

Variants

Four visual fill styles — solid, soft, outline, and ghost. Solid is the high-emphasis tier (connectivity bars, must-see failures); soft is the default.

Sizes

Three sizes — sm, md, and lg — control text size and padding.

Body only

No title: the icon rides the first line of the message — or drop the icon for a text-only strip beside a destructive control. A bold run-in lead lives in children.

With actions

Trailing placement (default): actions sit beside the message, vertically centred, with the dismiss control alone in the corner. When the message needs the room — a long title, a narrow column — they drop below it and line up with the text, rather than squeezing it into a column too narrow to read.

Actions below

actionsPlacement="below" keeps the buttons in the text flow at every width.

Links

Inline links inside the body, and href actions that render real anchors dressed as buttons.

Error summary

The post-submit pattern: titleAs="h2" for a real heading, list items linking to the failing fields, and autoFocus to move keyboard focus to the summary — the one sanctioned exception to “an alert never moves focus”.

Dismissible

onClose renders the ×; you own the state. Offer dismissal on informational and transient notices — never on destructive warnings or anything the user must resolve.

Live region roles

Roles derive from intent: error/warning announce assertively, the rest politely. role="none" opts a static callout out of the announcement stream entirely.

Acknowledgeable notice

Body copy with quiet text actions below — “Got it” / “Don't show again”. Also the recommended shape for AI-provenance notes when one must be boxed at all.

Rounded

The §2 cascade: shell, action and dismiss step together from none to full (the panel capsule cap).

Curving your own content

Content you put inside — a thumbnail beside the message — should curve like the chrome around it, or it reads as pasted on. The alert panel publishes its inner radius as a CSS variable:

example.tsx
<img className="rounded-[var(--alert-item-radius)]" />

It tracks whatever the component is actually doing: the explicit step when rounded is set (8px at md), and 0 at rounded="none" — so your content squares off exactly when the container does.

RTL

Logical properties end-to-end — icon, text, actions and dismiss all mirror under dir="rtl". Leave dir unset and the alert inherits the page's direction.

On color

A ghost alert paints no panel, so inside a coloured Surface its text, intent icon and action take the plate's inks — no tone prop. solid paints its own plate and hands its content on-color; soft and outline paint a page panel and hand it the default tone.

Loading

AlertSkeleton is the same box as the Alert — size, rounded, chrome and actions placement all mirror, so nothing jumps when content arrives.

API Reference

Alert

The root also forwards every standard div attribute (id, data-*, aria-*, style, event handlers) — title and role are the two it repurposes.

PropTypeDefaultDescription
intent"none" | "error" | "warning" | "success" | "info""none"Semantic colour, default icon, derived role
variant"solid" | "soft" | "outline" | "ghost""soft"Visual fill style
size"sm" | "md" | "lg""md"Text size and padding
rounded"none" | "sm" | "md" | "lg" | "xl" | "full""md"Border radius — nested controls follow (§2). full is the panel capsule cap
width"narrow" | "default" | "wide""default"Max-width constraint — narrow (sm), default (full), wide (4xl)
titleReactNode—Heading text
titleAs"p" | "h2" | "h3" | "h4" | "h5" | "h6""p"Render the title as a real heading when the alert opens a section
descriptionReactNode—Supporting body text
childrenReactNode—Free-form body below the description — lists, links, paragraphs
iconReactNodeIntent defaultCustom leading icon (skips the optical nudge)
showIconbooleantrueShow or hide the leading icon
onClose() => void—Renders the dismiss (×) button; visibility state is yours
closeLabelstring"Dismiss" (+ title)Accessible label for the × — localize it here
actionAlertAction—Primary CTA
secondaryActionAlertAction—Secondary CTA beside the primary
actionsPlacement"trailing" | "below""trailing"Trailing = beside the message, centred, dropping below it when the text needs the width; below = in the text flow always
shadowbooleanfalseDrop shadow — floating callout style
dir"ltr" | "rtl"inheritedText direction. Stamped only when set
role"alert" | "status" | "none"derivedLive-region role — derived from intent when omitted
autoFocusbooleanfalseFocus the alert on mount — error-summary pattern only
announceOnMountbooleanfalseMake a polite (status) alert speak when inserted — mounts the region empty, then fills it. Ignored for alert and none
classNamestring—Extra classes on the root element

AlertAction

FieldTypeDescription
labelstringButton text
onClick() => voidClick handler
hrefstringRenders the action as a real link (<a>), Button-dressed
target / relstringLink attributes — _blank defaults rel to noopener noreferrer
variantButtonProps["variant"]Override button fill style
intentButtonProps["intent"]Override button intent colour
roundedButtonProps["rounded"]Override button radius (defaults to the alert's rounded)
disabledbooleanDisabled state
loadingbooleanLoading spinner state
leadingIcon / trailingIconReactNodeIcons around the label

AlertSkeleton

Mirrors the Alert's box (§2b): variant, intent, size, rounded, width, shadow, dir, plus showIcon, showTitle, lines, showAction, showSecondaryAction, actionsPlacement, closeable.

Variants

VariantDescription
solidFilled background using the full intent colour. High emphasis — connectivity bars, must-see failures. Publishes the on-color tone, so a Button, Badge or Link in title, description or children adapts to the fill.
softLight tinted background (bg-error-light) with semantic icon colour. Default. Publishes the page's tone, so content in a soft alert inside a coloured Surface renders as it does on the page.
outlineColoured border + very subtle tint, laid over the page background. Opaque on any ground, so like soft it publishes the page's tone inside a coloured Surface.
ghostNo background or border. Icon still carries the semantic colour. Minimal emphasis. Inside a coloured Surface its text and icon take the plate's inks.

Intents

IntentIconDerived roleUse case
noneInfostatusNeutral informational message
errorCircleXalertFailure, validation summary
warningTriangleAlertalertNeeds attention before it becomes an error
successCircleCheckstatusPersistent confirmation (transient success → toast)
infoInfostatusGuidance, context

Accessibility

Pattern: WAI-ARIA Alert.

  • Roles derive from severity — error/warning render role="alert" (assertive), success/info/none render role="status" (polite), and role overrides both. Content rendered with the page announces nothing, whatever the role (use role="none" to opt static callouts out entirely).
  • An inserted role="alert" is announced; an inserted role="status" may not be. Verified on 2026-09-07 with VoiceOver in both Safari and Chrome: injecting an error alert spoke immediately, while injecting a success alert stayed silent in both browsers. This is the live-region rule, not a bug in the component — a polite region is expected to be in the DOM before its content changes, and only alert is special-cased to speak on insertion. If a non-urgent message must be heard the moment it appears, pass announceOnMount — the alert mounts its region empty and fills it a beat later, so the insertion reads as the content change a polite region needs. Verified with VoiceOver in Safari: the same success alert is silent without the prop and spoken with it. The prop is opt-in because doing it to every status alert would make the ones already on the page speak at load.
  • Focus is never moved by an alert. The one sanctioned exception is autoFocus for the post-submit error summary (tabIndex={-1} + visible focus ring), pairing the summary with per-field errors.
  • Dismissing hands focus on — and that is yours. onClose reports intent and you remove the alert, so you also place focus: on whatever replaces it, or on the control that raised it. Leave it and a keyboard user who pressed × lands on the page body. The Dismissible, Acknowledgeable notice and Error summary examples each show one way.
  • The leading icon is decorative (aria-hidden); meaning never rides on color alone — the intent word belongs in the text.
  • The dismiss button's name comes from closeLabel (the default is "Dismiss", enriched with the title when it is a plain string). Actions read as buttons, href actions as links.
  • Content wraps — titles and bodies are never truncated mid-thought (break-words handles long tokens/URLs).
  • Touch: every control clears 44×44px on phones via the Button/IconButton floors; actions drop below the message whenever it needs the width, so they never compress the text on a narrow column.

Keyboard

KeyBehaviour
Tab / Shift+TabMoves through action(s), then the dismiss button — natural order, no trap
Enter / SpaceActivates the focused action or dismiss
EscNothing — the alert is not modal (that's AlertDialog)

AT matrix

ATStatus
VoiceOver + Chrome (macOS)Passed 2026-09-07 — role announced as "alert", dismiss read as "Dismiss" plus the alert title, titleAs="h2" read as "heading level 2", autoFocus summary read in full. Caveat above: an inserted polite region did not speak
VoiceOver + Safari (macOS)Passed 2026-09-07 — an inserted error alert spoke immediately; the dismiss read as "Dismiss" plus the alert title; the polite case below reproduced
NVDA + Chrome (Windows)UNTESTED — no Windows host available
JAWSUNTESTED

Component stays beta until one VoiceOver and one NVDA pass are on record (QA flow §5.6).

MediaTrimmerBanner

On this page

When to useWhen not to useInstallationUsageExamplesDefaultIntentsVariantsSizesBody onlyWith actionsActions belowLinksError summaryDismissibleLive region rolesAcknowledgeable noticeRoundedCurving your own contentRTLOn colorLoadingAPI ReferenceAlertAlertActionAlertSkeletonVariantsIntentsAccessibilityKeyboardAT matrix