opsinjs
ComponentsHealth communication

AlertBanner

A message that appears at the top of a screen because something needs your attention now, saying what has happened and what to do about it.

Preview

Theme
Density
Text
Status
alert-banner · base base · style base-lyraOpen under the product theme

Installation

pnpm dlx shadcn@latest add @opsinjs/alert-banner

The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.

Usage

import { AlertBanner } from "@/components/ui/alert-banner"
<AlertBanner
  status="attention"
  heading="Your example measurement is outside the range your clinic set"
  detectedAt="2026-03-14T09:40:00+00:00"
  actions={[
    { label: "Contact your clinic", href: "/clinic" },
    { label: "See the reading and the range", href: "/results/example" },
  ]}
>
  Contact your clinic before your next appointment. This reading is outside the range they
  asked us to tell you about.
</AlertBanner>

When to use it

Use it when

  • Something has changed since the reader last looked, and they need to know before carrying on.
  • A reading has crossed a threshold the product owns, with a specific action attached.
  • A condition affects the whole screen: stale data, a disconnected device, an unfinished sync.

Do not use it when

  • Information the reader can take or leave, where nothing changes if they do not read it.Use callout instead.
  • The status belongs to one result, not the screen.Use status-pill instead.
  • The content is an instruction with steps and timing, not an announcement.Use care-card instead.
  • The reader must respond before anything else can happen.Use dialog instead.
  • Someone has disclosed thoughts of self-harm or suicide, which is not the status axis. See Crisis and self-harm.Use care-card instead.

Clinical meaning

Asserts. That the product has decided this screen needs the reader's attention at one of four levels, with something specific to do at attention and urgent. The component compares nothing: status is assigned from a range the product owns.

Never read as. A clinical judgement about the reader, an instruction from a person, or a substitute for emergency services. A finding routed stop and call for help now leaves the banner. See Emergency and escalation.

Colour axis. Status only. There is no category prop, so a banner never carries category colour. Urgency is never carried by motion either, and the component ships no transition. See Motion in health UI.

Thresholds. None. No staleness default and no number in the component. The level's word comes from the composed StatusPill, never from heading.

Budget. At most one urgent banner and two banners per screen, per Alarm fatigue. The component cannot count across a screen. No live region mounts at steady or watch, a named departure from Clinical status semantics rule 6.

Anatomy

  1. AlertBannerThe root, carrying data-status and the level's surface, ink and line roles.Controlled by status
  2. AlertBanner.AnnouncementThe live region around the heading and body only. Its role follows the level.
  3. AlertBanner.HeadingA real h2 to h6: the level's word, then the caller's subject.Controlled by headingLevel
  4. StatusPillThe level's word and glyph, composed as the heading's first child.
  5. AlertBanner.BodyOne or two sentences, second person. Longer is a CareCard.Controlled by children
  6. AlertBanner.ActionsAt most two, each a composed Link or Button. The first takes the emphasis.Controlled by actions
  7. AlertBanner.DismissA quiet Button, last in the DOM. Rendered only with dismissible and onAcknowledge.Controlled by dismissible

Examples

Two is the ceiling

Two banners, the more serious first, because a reader meets them in DOM order. A third teaches the reader to scroll past the second.

Theme
Density
Text
Status
alert-banner-two-is-the-ceiling · base base · style base-lyraOpen under the product theme

An urgent banner is acknowledged, not dismissed

dismissible renders nothing without onAcknowledge, and at urgent that callback is the record. One action, because the route is same-day. Removing the banner drops focus to <body>, which the product must recover.

Theme
Density
Text
Status
alert-banner-acknowledging-an-urgent-banner · base base · style base-lyraOpen under the product theme

steady is for de-escalation

The one honest use of the quietest level: withdrawing an alert the product raised earlier. No actions, because only attention and urgent require one.

Theme
Density
Text
Status
alert-banner-steady-is-for-de-escalation · base base · style base-lyraOpen under the product theme

Accessibility

Audited against WCAG 2.2 AA in a source pass and a rendered pass. This is an author-run audit, not an independent review, and clinical review is still pending. The rendered pass was clean, with no axe violation and no target-size or focus-visible gap. The source pass found nothing to change.

Confirmed: colour never carries meaning alone, because the word and glyph come from the composed StatusPill and shape and position separate the levels. The live region wraps the heading and body only, role="alert" at urgent and aria-live="polite" with aria-atomic at attention. Focus is never moved and every control clears the 44pt floor.

Two residuals. At steady and watch the component mounts no live region, so a de-escalation banner inserted after a condition resolves is not announced to a reader already on the page. That is a deliberate departure from Clinical status semantics rule 6 on alarm-fatigue grounds, a doctrine call left for a human reviewer. Escalating in place also relies on the caller passing key={status} to remount, which the component cannot enforce.

Still ungated: any real screen reader, forced-colors, and the root's missing accessible name.

KeyActionNotes
TabMoves through the actions, then dismissThe banner itself is not a tab stop.
Shift+TabThe same in reverse
EnterFollows a link action or activates a buttonhref renders an anchor, onSelect a real button.
SpaceActivates a button action or the dismissOn a link action Space scrolls the page.
EscapeNothingThe dismiss control is the only way out.
PairThemeAPCA LcWCAG 2.2Floor
attention ink on attention surfacelight89.410.35:1Pass
attention ink on attention surfacedark-81.112.54:1Pass
attention line on attention surfacelight70.04.90:1Pass
attention line on attention surfacedark-65.59.54:1Pass
attention accent on attention surfacelight65.14.18:1Pass
attention accent on attention surfacedark-49.46.80:1Pass
attention ink on the attention bandlight22.42.48:1Below floor
attention ink on the attention banddark-31.01.84:1Below floor
attention accent on the pagelight67.84.35:1Pass
attention accent on the pagedark-50.57.47:1Pass
attention ink on the pagelight92.210.77:1Pass
attention ink on the pagedark-82.313.76:1Pass
steady ink on steady surfacelight88.39.62:1Pass
steady ink on steady surfacedark-82.912.56:1Pass
steady line on steady surfacelight67.54.45:1Pass
steady line on steady surfacedark-68.99.91:1Pass
steady accent on steady surfacelight56.43.21:1Pass
steady accent on steady surfacedark-53.27.25:1Pass
steady ink on the steady bandlight30.03.00:1Below floor
steady ink on the steady banddark-28.81.73:1Below floor
steady accent on the pagelight58.63.31:1Pass
steady accent on the pagedark-54.58.19:1Pass
steady ink on the pagelight90.69.94:1Pass
steady ink on the pagedark-84.214.20:1Pass
unknown ink on unknown surfacelight89.59.98:1Pass
unknown ink on unknown surfacedark-82.212.56:1Pass
unknown line on unknown surfacelight69.24.64:1Pass
unknown line on unknown surfacedark-67.29.72:1Pass
unknown accent on unknown surfacelight43.12.29:1Below floor
unknown accent on unknown surfacedark-51.37.05:1Pass
unknown accent on the pagelight45.62.37:1Below floor
unknown accent on the pagedark-52.37.84:1Pass
unknown ink on the pagelight92.010.34:1Pass
unknown ink on the pagedark-83.113.97:1Pass
urgent ink on urgent surfacelight88.210.58:1Pass
urgent ink on urgent surfacedark-80.312.52:1Pass
urgent line on urgent surfacelight70.05.09:1Pass
urgent line on urgent surfacedark-64.89.52:1Pass
urgent accent on urgent surfacelight72.35.58:1Pass
urgent accent on urgent surfacedark-48.06.63:1Pass
urgent ink on the urgent bandlight13.91.90:1Below floor
urgent ink on the urgent banddark-31.51.89:1Below floor
urgent accent on the pagelight75.35.84:1Pass
urgent accent on the pagedark-49.57.24:1Pass
urgent ink on the pagelight91.211.06:1Pass
urgent ink on the pagedark-81.813.66:1Pass
watch ink on watch surfacelight89.410.02:1Pass
watch ink on watch surfacedark-82.112.58:1Pass
watch line on watch surfacelight69.54.72:1Pass
watch line on watch surfacedark-66.89.68:1Pass
watch accent on watch surfacelight61.73.72:1Pass
watch accent on watch surfacedark-50.76.95:1Pass
watch ink on the watch bandlight25.72.70:1Below floor
watch ink on the watch banddark-30.61.81:1Below floor
watch accent on the pagelight64.23.85:1Pass
watch accent on the pagedark-51.77.71:1Pass
watch ink on the pagelight91.910.38:1Pass
watch ink on the pagedark-83.113.95:1Pass
body text on the pagelight101.617.18:1Pass
body text on the pagedark-100.518.00:1Pass
body text on a cardlight104.717.96:1Pass
body text on a carddark-99.616.32:1Pass
body text on the muted groundlight98.116.32:1Pass
body text on the muted grounddark-97.513.77:1Pass
secondary text on the pagelight83.97.42:1Pass
secondary text on the pagedark-80.713.44:1Pass
secondary text on a cardlight87.07.76:1Pass
secondary text on a carddark-79.812.19:1Pass
secondary text on the muted groundlight80.47.05:1Pass
secondary text on the muted grounddark-77.810.28:1Pass
a hairline boundary on the pagelight19.31.41:1Below floor
a hairline boundary on the pagedark-8.21.90:1Below floor
a hairline boundary on a cardlight22.41.47:1Below floor
a hairline boundary on a carddark-7.31.72:1Below floor
a hairline boundary on the muted groundlight15.81.34:1Below floor
a hairline boundary on the muted grounddark0.01.45:1Below floor
a placeholder boundary on the pagelight69.84.52:1Pass
a placeholder boundary on the pagedark-51.07.62:1Pass
a placeholder boundary on a cardlight72.84.72:1Pass
a placeholder boundary on a carddark-50.16.91:1Pass
a placeholder boundary on the muted groundlight66.24.29:1Pass
a placeholder boundary on the muted grounddark-48.15.83:1Pass
a placeholder fill on the pagelight8.31.18:1Below floor
a placeholder fill on the pagedark-8.21.90:1Below floor
a placeholder fill on a cardlight11.41.23:1Below floor
a placeholder fill on a carddark-7.31.72:1Below floor
a placeholder fill on the muted groundlight0.01.12:1Below floor
a placeholder fill on the muted grounddark0.01.45:1Below floor
a placeholder boundary on its own filllight58.93.84:1Pass
a placeholder boundary on its own filldark-41.54.01:1Below floor
the card hairline on the pagelight69.84.52:1Pass
the card hairline on the pagedark-28.74.19:1Below floor
the card hairline on a cardlight72.84.72:1Pass
the card hairline on a carddark-27.93.80:1Below floor
attention surface on the card materiallight0.01.09:1Below floor
attention surface on the card materialdark0.01.00:1Below floor
steady surface on the card materiallight0.01.08:1Below floor
steady surface on the card materialdark0.01.03:1Below floor
urgent surface on the card materiallight0.01.09:1Below floor
urgent surface on the card materialdark0.01.01:1Below floor
watch surface on the card materiallight0.01.08:1Below floor
watch surface on the card materialdark0.01.01:1Below floor
the focus ring on the attention surfacelight67.34.44:1Pass
the focus ring on the attention surfacedark-45.96.30:1Pass
attention ink on the neutral hover filllight88.610.23:1Pass
attention ink on the neutral hover filldark-79.310.53:1Pass
the focus ring on the steady surfacelight67.84.47:1Pass
the focus ring on the steady surfacedark-45.86.12:1Pass
steady ink on the neutral hover filllight87.09.43:1Pass
steady ink on the neutral hover filldark-81.310.86:1Pass
the focus ring on the urgent surfacelight67.04.42:1Pass
the focus ring on the urgent surfacedark-45.56.34:1Pass
urgent ink on the neutral hover filllight87.710.50:1Pass
urgent ink on the neutral hover filldark-78.910.46:1Pass
the focus ring on the watch surfacelight67.64.46:1Pass
the focus ring on the watch surfacedark-46.06.24:1Pass
watch ink on the neutral hover filllight88.39.86:1Pass
watch ink on the neutral hover filldark-80.110.67:1Pass

These are the measured token pairs this component draws colour from, not a measurement of the component itself.

Data attributes

AttributeConditionValue
data-slotOn the root, alwaysalert-banner
data-slotOn inner partsalert-banner-announcement, -heading, -heading-text, -body, -actions, -action, -dismiss
data-statusOn the root, alwayssteady | watch | attention | urgent
data-detected-atOn the root, when detectedAt is non-emptyThe ISO 8601 instant, verbatim. Absent otherwise.

API reference

Prop

Type

Generated from AlertBannerProps in registry/bases/base/alert-banner.tsx.

A status outside the four levels renders nothing and reports OPSIN-0021 (OPSIN-0011 for unknown). heading names the subject, because the component renders the level's word. headingLevel outside 2 to 6 falls back to h2. A third action renders and is reported. dismissible needs onAcknowledge at every level. now, locale and timeZone render nothing. AlertAction is also exported:

export interface AlertAction {
  label: string
  href?: string
  onSelect?: () => void
  recommended?: boolean
}
  • Callout carries no clinical level and interrupts nothing.
  • CareCard is the instruction itself, with steps and timing.
  • StatusPill is the same vocabulary on one item, not the screen.
  • Toast is transient confirmation. Nothing clinical belongs on a timer.
  • Dialog is for when the reader cannot continue until they respond.

On this page