DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

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.

Playground

Installation

pnpm add @tessinaui/ui

Usage

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 }}
/>

When to use

Alert sits in the middle of the feedback ladder — pick the surface by lifetime and blast radius, not by color:

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
  • Route success to a toast unless it is delayed, persistent, or carries a CTA.
  • Place the alert next to what it describes; avoid more than one per page — combine messages instead.
  • Field-level validation belongs to Field errors; the alert is the form-LEVEL summary (see Error summary).

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 (Fluent's MessageBar reflow).

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:

<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".

Loading

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

Variants

VariantDescription
solidFilled background using the full intent colour. High emphasis — connectivity bars, must-see failures.
softLight tinted background (bg-error-light) with semantic icon colour. Default.
outlineColoured border + very subtle tint.
ghostNo background or border. Icon still carries the semantic colour. Minimal emphasis.

Intents

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

Props

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""ltr"Text direction
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.

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.
  • 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).

Action SheetAlertDialog

On this page

PlaygroundInstallationUsageWhen to useExamplesDefaultIntentsVariantsSizesBody onlyWith actionsActions belowLinksError summaryDismissibleLive region rolesAcknowledgeable noticeRoundedCurving your own contentRTLLoadingVariantsIntentsPropsAlertAlertActionAlertSkeletonAccessibilityKeyboardAT matrix