Alert Banner
A persistent, priority-ranked message that tells a clinician what has changed, how urgent it is, and what to do next — without taking over the screen.
AcuteLine is a fictional SaMD used as the worked example across this system. It ingests 12-lead and continuous ECG plus high-sensitivity troponin results and flags patients with suspected acute coronary syndrome (ACS) for clinician review. It is decision support: it never diagnoses, never triggers therapy, and every output is confirmed by a qualified clinician. All screens, copy and patient data are fabricated.
Overview
An Alert Banner occupies a fixed region of the layout and stays there until the underlying condition resolves or a clinician acts on it. It is the workhorse for communicating change in a SaMD: a new detection, a degraded input, a failed analysis, a completed action.
In AcuteLine it carries the moment that matters most — the transition from "ECG received" to "this patient may be having a STEMI." That transition has to survive a noisy resuscitation bay, a clinician looking at a different monitor, and a screen three metres away.
When to use
- A detection or measurement crosses a threshold that requires a clinician to look or act.
- An input the algorithm depends on has degraded — lead off, signal noise, missing lab result.
- Analysis has failed, been skipped, or produced an indeterminate result.
- An action the clinician started has completed and the outcome must be visible on screen.
When not to use
- To confirm trivial interactions. Saving a filter needs no banner.
- To block a workflow. Use
<Dialog>. A banner must never be the only thing between a clinician and an urgent task. - For per-field validation. Errors belonging to one input belong under it.
- As a toast, at Critical or Urgent. Never use
<Sonner>for a clinical alarm. An alarm you can miss by looking away is a hazard, not a convenience.
Design principles
Unambiguous
Priority, patient and finding must be readable in one glance, from across the room, by someone mid-task. If a clinician has to read it twice, the banner has failed.
Proportionate
Visual force matches clinical consequence — never engagement value. Every banner promoted above its true priority spends attention the next real alarm will need.
Actionable
State the next step and put the control to take it inside the banner. An alert that reports a problem but leaves the clinician hunting has moved work, not removed it.
Anatomy
Priority levels
Four priority levels plus one confirmation level. The first three map onto IEC 60601-1-8 alarm priorities and inherit that standard's mandated hues. These are not brand decisions and must not be re-themed — see Colour.
| Level | IEC | Means | AcuteLine example | Token |
|---|---|---|---|---|
| critical | High | Immediate response required | STEMI pattern detected | --alarm-critical |
| urgent | Medium | Prompt response required | hs-troponin rising on 0/1 h algorithm | --alarm-urgent |
| advisory | Low | Awareness; can wait | Lead V3 signal quality degraded | --alarm-advisory |
| info | Not an alarm | State change, no response | Analysis complete — no acute pattern | --alarm-info |
| confirmation | Not an alarm | A clinician action succeeded | Cath lab notified at 14:32 | --alarm-ok |
Physiological and technical alarm conditions
Priority says how urgently. It does not say what kind of thing is wrong, and IEC 60601-1-8 separates two kinds that this page had been rendering identically.
| Kind | Arises from | What it actually tells the clinician |
|---|---|---|
| Physiological | A monitored patient variable | Something about the patient has changed. The system is working and is telling you what it found. |
| Technical | The device, a sensor, a connection, a source system | The system can no longer tell you about the patient. Not a finding — the absence of one. |
The advisory in the demo above reads "Lead V3 signal quality degraded — anterior interpretation may be limited." That is a technical alarm condition wearing the same banner as the two physiological ones beside it, with nothing to say so.
A clinician scanning a stack of banners cannot tell "this patient is deteriorating" from "we have stopped being able to see this patient" — and the second is easy to read as a nuisance precisely because nothing about the patient has changed yet.
What follows
- The kind is stated, in the banner, in words. A technical alarm says so: "Technical — lead V3 signal lost". One word, and it changes what the reader does next.
- The kind never changes the hue. Priority owns the colour, per Colour. A technical alarm at high priority looks exactly as urgent as a physiological one at high priority, because it is — a monitor that has stopped monitoring an unstable patient is not a lesser event.
- A technical alarm names what is no longer being watched. "Lead V3 signal lost" is a device fact. "Anterior interpretation unavailable until re-acquired" is the clinical consequence, and it is the half that decides what happens next.
- Resolving is different for each. A physiological alarm resolves when the patient variable returns to range. A technical one resolves when monitoring is restored — and until it is, the absence of physiological alarms means nothing. That is the sentence a clinician needs and almost never gets.
- Acknowledging a technical alarm never implies the patient is fine. It records that somebody knows monitoring is degraded, which is a different assertion from acknowledging a finding — see Acknowledge & escalate.
Messages about the software
A third category sits outside both: the application reporting on itself. A failed export, an unreachable directory, a session about to expire. These are not alarm conditions and never take an alarm hue, however severe they feel to the person who built them.
- The alarm palette is reserved for the patient and for the device watching them. A red banner about a failed export teaches a clinician that red sometimes means "a background job did not run", and every real alarm afterwards is read through that lesson — the dilution argument from Colour.
- The boundary is the patient, not the severity. A completely broken export is still not an alarm; a degraded sensor on a monitored patient always is. Severity decides priority within a category, never which category something is in.
- Where an application failure has a clinical consequence, the consequence is the alarm — not the failure. "Pathology feed unreachable" is infrastructure; "results have not been checked since 04:10 for 12 patients" is a technical alarm condition, and it is the one that belongs in this component.
- Everything else routes through Messaging, which decides the vehicle by what the message obliges the reader to do.
The headline
- An alarm headline is never truncated. Offered widely as a formatting option, and it is the one string on the screen that must survive intact: a headline reading "hs-troponin rising — NSTEMI cannot be…" has clipped the half that matters, and the tooltip carrying the rest does not exist on a cart.
- Two lines is the budget, and it is a budget on the writing rather than on the container. If it does not fit in two lines, the detail belongs in the description.
- The headline names the finding, not the algorithm. "STEMI pattern detected", never "Rule 14 triggered" — see Voice & tone.
- It is stable. A headline that rewrites itself as the condition evolves is a headline a clinician cannot quote to a colleague on the phone. The description updates; the headline does not.
Variants
docked
Full-bleed, solid fill, pinned beneath the patient header. The only variant permitted for
critical. Use when the alert concerns the whole record in view.
inline
Scoped to a card or panel: surface drops to --card and priority moves to a
4 px rail. Use when the alert concerns one region — a single lead, one lab panel, one chart.
The lower chroma is deliberate; an inline alert must not out-shout a docked one on the same screen.
compact
Single line, no description or provenance. Permitted only inside dense repeating structures such as a triage worklist, and only when the full alert is one click away.
States & behaviour
| State | Appearance | Rules |
|---|---|---|
| active | Full priority chroma | Entry 220 ms ease-out. Never re-animate on re-render. |
| acknowledged (timed) | Chroma drops to rail; countdown shown | Returns to active automatically when the timer lapses if the condition persists. |
| acknowledged (indefinite) | Chroma drops to rail | Persists until resolved. Writes user and timestamp to the audit log. |
| audio-paused | Visually unchanged; separate audio indicator | Pausing audio must never alter or hide the visual signal. |
| latched | Full chroma retained | Condition resolved but was never seen. Stays until acknowledged. |
| resolved | Removed, or replaced by a confirmation | Critical alerts are never removed silently — always leave an event-log trace. |
Stacking and coalescence
- Show at most three docked banners. Beyond three, collapse the lowest priorities into a summary row — never the highest.
- Sort strictly by priority, then most-recent-first within a priority.
- An arriving
criticalrenders above everything and enters the focus order, but must not steal keyboard focus mid-typing. - Coalesce only alerts identical in patient, finding and priority; show count and latest time.
Writing the message
Alert copy is a safety surface. Write for a reader who is interrupted, tired, reading at distance, and about to make a decision.
- Title ≤ 60 characters. Front-load the finding, not the software.
- Name the anatomy or measurement. "Anterior leads V2–V4", "ST elevation 3.1 mm" — specificity is what makes an alert verifiable.
- Say what to do in one imperative sentence, referring to local protocol rather than prescribing therapy.
- Never claim a diagnosis. Use "pattern detected", "cannot be excluded", "consistent with". The software detects; the clinician diagnoses.
- Avoid negation stacks. "Not non-diagnostic" is unreadable at 3 a.m.
- Budget 35 % for translation. Test German and Finnish before release.
Names the finding, the territory and the measurement, then hands the decision back to the clinician.
Claims a diagnosis the software is not cleared to make, leads with the product name, and gives no territory, measurement or action.
What the component enforces, and what it only asks for
This is the page where the difference matters most, because the component renders an alarm and almost none of what makes an alarm system safe happens inside a renderer. It has no state, no timer and no knowledge of the condition it is displaying.
| Rule | Held by | How |
|---|---|---|
| Priority is one of the IEC 60601-1-8 codings | The component | priority is a union of five values. A severity outside the standard's coding is not expressible.Asserted: AlertBanner: priority is a closed set, so a sixth severity is unwritable |
| An alarm always carries a statement | The component | title is required, so a banner that signals without saying anything cannot be built.Asserted: AlertBanner: an alarm cannot be raised without a statement |
| Physiological and technical are distinguished | The component | kind renders the distinction, so a lead-off is not read as a patient deteriorating.Asserted: AlertBanner: a technical condition says so in words |
| Acknowledgement records who and when | The component | acknowledgedBy takes a name and a time, and renders both.Asserted: AlertBanner: acknowledged state names the person, and the control goes |
| Colour is never the only carrier | The component | Priority renders as a word and a fill together; there is no colour-only mode. Asserted: AlertBanner: priority is in text, not only in colour |
| An alarm condition is announced at once, and nothing at any priority can be dismissed | The component | A critical or urgent condition renders as an aria-live="assertive" region; there is no dismiss control in the props, so the only way off the screen is acknowledgement or resolution.Asserted: AlertBanner: a critical condition is an assertive live region, and has no dismiss |
| Acknowledging does not throw focus away | The component | The control is removed once pressed, and focus moves to the acknowledgement that replaces it. It went to <body>: a keyboard user who had just acknowledged an alarm was left at the top of the document. Driven in a browser.Asserted: AlertBanner: acknowledging by keyboard leaves focus on the acknowledgement |
| An acknowledged alarm re-asserts if the condition persists | You | There is no timer here. onAcknowledge tells you it happened; whether the condition is still true, and what to do about it, is your alarm system's job. |
| Escalation after an unattended interval | You | Also a timer, also yours. The component cannot know the interval or that nobody attended. |
| The banner is never covered, and never scrolls away | You | That is a layout decision — see Grid & layout. Putting this inside a scrolling container defeats every rule on this page and nothing here can detect it. |
| Alarms are not raised faster than a person can act on them | You | Rate is a property of your alarm logic. A renderer will draw whatever it is handed, including a queue that trains a clinician to dismiss without reading. |
| An audible signal accompanies critical and urgent conditions | You | This package emits no sound at all — see Auditory alarms. |
A rule stated on a component page reads as a rule the component keeps. 5 of these 10 are; 5 are obligations this page was quietly handing to the reader. Five of the ten are the alarm system's, not this component's, and they include re-assertion, escalation and rate — the three that IEC 60601-1-8 actually turns on. This page renders alarms; it does not manage them.
Do's and don'ts
Match the level to the consequence. A recoverable input problem is advisory.
Inflating an electrode problem to critical trains clinicians to discount red — the definition of alarm fatigue.
State the limits of a negative result in the banner itself.
Reads as an all-clear the software cannot give, and invites premature discharge.
Provenance on every algorithm-derived alert: who, when, which model, how sure.
No patient, no time, no model version. Correct on the wrong record and nobody could tell.
A recoverable input problem, announced so the clinician knows the interpretation is limited.
Silent about the degraded lead. The result looks as trustworthy as any other.
Names the kind and the consequence. The sentence that matters is the second one, and a clinician almost never gets it.
Identical to a physiological banner beside it. The reader cannot tell "the patient is deteriorating" from "we have stopped being able to see the patient".
The clinical consequence is the alarm. "Pathology feed unreachable" is the infrastructure fact underneath it, and belongs in the description.
High-priority alarm red for a background job. Every real alarm afterwards is read through the lesson that red sometimes means this.
Two lines of budget, spent on the finding. Quotable to a colleague on the phone, word for word.
Clipped at "cannot be…", which is the half that decides what happens. The tooltip carrying the rest does not exist on a cart.
Accessibility
- Never encode priority in colour alone. Every level pairs its hue with a distinct glyph and a distinct word in the title. Verify in greyscale.
- Contrast. Every fill meets AA against its paired foreground. Amber, cyan and grey carry black text — white on amber measures 1.68:1 and is a hard failure. Do not "fix" them to white for visual consistency.
- Live regions.
criticalandurgentrenderrole="alert"; the rest renderrole="status". Never make five banners assertive at once. - Focus. Arriving banners must not steal focus. Provide a documented shortcut that jumps to the highest-priority banner.
- Motion. Under
prefers-reduced-motion, render with no transition — never with no alert. - Zoom and reflow. Operable at 200 % zoom and a 320 px viewport with no horizontal scrolling; actions wrap below the text rather than truncating.
- No flashing. Escalate by priority, audio and position — never by flicker.
Measured contrast
| Fill | Foreground | Ratio | Result |
|---|---|---|---|
--alarm-critical |
White | 5.64:1 | AA |
--alarm-urgent |
Black | 12.49:1 | AAA |
--alarm-advisory |
Black | 8.78:1 | AAA |
--alarm-info |
Black | 10.98:1 | AAA |
--alarm-ok |
White | 6.07:1 | AA |
Critical and confirmation sit in the AA band, not AAA. Reaching 7:1 would darken the red past the point where it reads as an alarm hue, which IEC conformance depends on. The trade is deliberate and belongs in your accessibility conformance report.
Outcomes of use
What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.
- Effectiveness — The correct priority is recognised at a glance and the finding is acted on for the right patient. Measured by correct-priority recognition and by wrong-patient actions per 1 000 alerts.
- Efficiency — Time to acknowledge, by priority, and the attention each alert consumes. Both degrade as alarm burden rises — see Acknowledge & escalate.
- Satisfaction — Whether a clinician believes an alert at this priority is worth stopping for. Every alert promoted above its true priority spends that belief, and it is not quickly rebuilt.
Clinical safety notes
These behaviours mitigate identified hazards and should be traced in your risk file (ISO 14971). Changing them is a design change with safety consequence, not a styling decision. Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.
- Nothing here dismisses anything, at any priority. (the component) There is no dismiss prop, no close control and no timer, so an advisory cannot quietly expire either. This bullet used to read “no auto-dismiss above advisory”, which two pages read as permission to dismiss an advisory. Mitigates missed detection on an unattended display.
- Provenance is mandatory. (you) Patient, time, model version and confidence on every algorithm-derived alert. Mitigates wrong-patient action and use of a superseded model.
- Confidence is shown, never hidden behind a hover. (you) Mitigates automation bias.
- Negative results state their own limits. (you) Mitigates false reassurance.
- Primary action opens evidence, (you) never an irreversible step such as notifying the cath lab. Mitigates acting on an unreviewed algorithmic output.
- Degraded input is announced, (you detect it; the component states it in words) not silently tolerated. Mitigates silent failure.
- Acknowledgement is attributable (the component) — user and timestamp to the audit log.
Implementation
This section published an implementation built on
class-variance-authority, importing cn from
@/lib/utils — shadcn’s path, not this package’s.
The registry page says of these components:
“None at runtime. Not clsx, not tailwind-merge, not Radix.” A reader following
that snippet added a runtime dependency to justify in a technical file, to build a component
that does not have one.
$ npx shadcn@latest add https://md.notjustany.tech/r/alert-banner.json
<AlertBanner
priority="critical"
kind="physiological"
title="STEMI pattern — anterior leads V2-V4"
description="ST elevation 3.1 mm. Confirm ECG and activate the chest-pain pathway."
provenance="Model 4.2 · confidence 0.94"
when="2026-08-13T14:26:38Z"
onAcknowledge={acknowledge}
actions={<Button variant="outline">Open ECG</Button>}
/>
// 'title' is required and 'priority' has five permitted values, so a banner
// with no statement, or with a severity outside IEC 60601-1-8's coding,
// cannot be written.
// The real construction: a lookup, not a variant library.
// alarmSurface maps each IEC 60601-1-8 priority to its token pair, and
// ships in foundations, so a banner cannot invent a sixth severity.
import { alarmSurface, isAlarmCondition } from "@/lib/njt"
The class joiner is six lines and ships inside foundations.
Swap in your own cn if you already have one — that is the only substitution
this package invites.