Agent docs

Alert

The inline callout for a note, warning, or error that belongs in the page flow.

The inline callout for a note, warning, or error that belongs in the page flow: a soft-tinted banner with a leading icon, not a transient event. Toasts are for the transient kind; see toast.md.

The canon behind it. docs/design-language/03-status-language.md §A7 (03-status-language.md:33, banner tone, placement, and never painting two outcomes the same colour) and docs/design-language/10-overlays.md §A8 (10-overlays.md:42, draws the Alert-versus-Toast line this doc opens with).

Toolkit sibling. packages/toolkit/src/result.ts (getApiError, failActionMessage) is the error-to-display-string half this banner renders; the toolkit README's module table names this doc as where result belongs (packages/toolkit/README.md, "Belongs with").

Border weight. The tone border sits at 18 percent of the line colour (was 35) and the neutral border at base-300/50, owner ruling doc 59 S1 ("50% opacity of the existing color"), so the frame reads as a soft surface with a hint of edge rather than an outlined box.

When to reach for it#

Reach for Alert wherever a message needs to sit inside the page's own layout and stay until the condition that caused it changes: a form-level validation error above the submit button, an account-suspended notice, a plan-limit warning. Every shipped call site in both apps is exactly this shape, a conditional banner rendered above or beside a form, never a fire-once notification.

Do not reach for it for a transient event (a save succeeded, a job finished). That is Toasts/pushAlert.

Contract#

  • tone: AlertTone, a five-way union (info | success | warning | error | neutral), default info. Each tone pairs a soft background/border/text triad with its own default icon (Info, Success, Warning, Error; neutral also defaults to Info) (alert.tsx:7-36).
  • title: goes through renderNode, so a plain string gets the bold treatment while JSX passes through untouched.
  • children: the body, always wrapped in one block (alert.tsx:79-81), so mixed children (a sentence with a link or a chip in it) flow inline as one paragraph rather than each child landing on its own line. bodyClassName styles that block; add flex flex-col gap-1 there for the rare stacked body.
  • Icon: overrides the tone's default icon. noIcon: drops the leading icon entirely. The icon sits vertically centred against the title-plus-body block (items-center, alert.tsx:63), so a one-line alert reads as one row and a two-line one keeps the icon at its middle.
  • actions: a trailing slot for buttons or links, rendered after the body.
  • markdown: when children is a plain string, parses it through the kit's tiny markdown-lite subset (bold, italic, inline code, links, newlines) instead of rendering the raw string. No-ops on JSX children. The same helper (maybeMarkdown) backs ConfirmModal's body (modal/confirm-modal.tsx:53); Tooltip renders the same inline subset through the sibling renderMarkdownLite call directly (tooltip/tooltip.tsx:129).
  • skeleton: a shimmer bar in the alert's own rounded shape, in place of content.
  • noAnimate: opts out of the animate-fadeInUp entrance the banner plays by default on mount (mirrors Card's noAnimate; skeleton mode never animates). On by default because an Alert is almost always conditional, arriving after a form submit or a fetch resolves, the same "this just changed" moment the convention targets.
  • Slot classnames per AlertClassNames: className the outer banner, titleClassName, bodyClassName.

Sanctioned combinations#

CombinationProducesWhere usedWhy
tone="error" with a plain string child, no other propsA field-level or form-level error banner above the submit buttonspeedway/app/routes/auth/forgot.tsx:71, speedway/app/routes/auth/login.tsx:92, walmart-mvp/frontend/src/pages/Signup.tsx:83, walmart-mvp/frontend/src/pages/Login.tsx:71The simplest shape covers the dominant real case: surface a server-returned error string exactly as sent
tone="warning" / tone="info", plain string childAn account-level advisory banner (over a plan limit, an overage total)speedway/app/components/usage/UsageOverview.tsx:45-56Three stacked alerts, one per condition (suspended, over-limit, overage), each reads its own tone rather than one banner switching color

Every shipped Alert in both apps passes only tone and a string child. title, Icon, noIcon, actions, markdown, and skeleton have no confirmed shipped call site as of this pass; treat them as implemented and correct, not as proven in production use.

Banned combinations#

Do not reach for Alert for a transient event: a save that succeeded, a job that finished, a copy that landed. The statistically likely wrong answer is an Alert color="success" rendered when a mutation resolves, which is how most libraries demo success feedback; here that event is pushAlert / Toasts. the canon draws the line in one sentence, "Alert is for in-flow notes. Toast is for transient events." (docs/design-language/10-overlays.md:43, docs/design-language/03-status-language.md:34).

Do not paint two different outcomes the same tone, and do not make one banner switch tone as conditions change. Render one Alert per condition, each in its own tone, the way the usage screen stacks suspended / over-limit / overage (speedway/app/components/usage/UsageOverview.tsx:44-56); the canon's banner rule is "the banner must not paint the two the same colour" (docs/design-language/03-status-language.md:34).

Do not render an outcome banner below the table or form it explains. Banners render above: "'Failed' and '0 parts' are both unreadable without it" (docs/design-language/03-status-language.md:34).

Do not pass a JSX array as children expecting one row per child. The body is one inline block on purpose (alert.tsx:79-81), so mixed children flow as a sentence; for the rare stacked body pass one node and set bodyClassName="flex flex-col gap-1".

Before you adopt this#

Five questions to answer before reaching for Alert.

  1. Does the shell, a parent layout, or a global provider already render this? Not applicable, Alert renders inline content; no shell or provider owns it.
  2. Does this app already ship a local implementation of the same thing? Not applicable, no local Alert clone is documented in either app.
  3. Does this app's kit pin reach the version this component or prop landed in? Not applicable, no version-landed Alert prop is noted in this doc.
  4. Does the component derive its own accessible name and keyboard path, or must the call site supply them? Not applicable, Alert has no control; actions buttons name themselves.
  5. Which canon §A rules bind this surface, and which does the composition break? §A7 bars two outcomes sharing one tone, or a banner below what it explains.

Travels with#

  • RenderIcon, internally, for the leading tone icon.
  • The internal markdown-lite helper, shared with ConfirmModal's body and Tooltip's content for the same "wrap **this** in bold without JSX" need.
  • Button or a link, in the actions slot, when a shipped example exists to copy from.

Snippet#

// speedway/app/components/usage/UsageOverview.tsx:44-56
{flags.suspended ? (
<Alert tone="error">
This account is suspended, contact Versable to restore access
</Alert>
) : null}
{!flags.suspended && flags.enforcement === "soft" && isOver ? (
<Alert tone="warning">
You are over one or more plan limits this month. Work continues for
now, contact Versable to raise your limits
</Alert>
) : null}
{overageDollars ? (
<Alert tone="info">Overage so far this month: {overageDollars}</Alert>
) : null}
@versable-git/ui · reference, canon, and method, read in place