---
title: "Escalate an alert without alarming"
description: "Move a message up the escalation ladder only as far as it has earned, and spend the screen's alarm budget on the one thing that deserves it."
url: "https://opsinjs.pensievelabs.org/recipes/staged-alert"
source: "https://opsinjs.pensievelabs.org/recipes/staged-alert.md"
section: "Recipes"
kind: "recipe"
evidence: "opinion"
reviewed: "2026-09-20"
reviewer: "clinical"
implements: ["alert-banner", "care-card", "status-pill"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="recipe" />

## The task [#the-task]

Something has happened that the person may need to know about: a reading outside
its usual range, a missed dose, a device that has stopped reporting, a result that
has arrived. You have to decide how loudly to say it.

The instinct is to make important things prominent, and it is wrong in aggregate.
Every product that treats prominence as free ends up with a screen where three
things are shouting, and a screen where three things are shouting is a screen
where nothing is. That is alarm fatigue, and it is the mechanism by which the one
alert that mattered gets dismissed with the others.

The discipline is a **ladder** and a **budget**. Each message starts at the bottom
of the ladder and climbs only as far as it can justify; the screen has a fixed
budget at the top, and if two things want it, one of them is not going to get it.

<SafetyCallout severity="attention" evidence="opinion">
  opsinjs never decides that something is urgent. The status is assigned by a
  clinical rule your product owns and can point to. A component that inferred
  urgency from a value would be making an unlicensed clinical judgement, and a
  recipe that told you to would be worse.
</SafetyCallout>

## What you need [#what-you-need]

Every rung of the ladder below is `shipped`: the code exists and installs, the
API may change in any release, and none of it has
been through an accessibility or a clinical review. What none of it does is
decide which rung a message belongs on. That is the clinical rule your product
owns, and it is the first step below.

| Part              | Component                                                             | Status                           | What it does here                                           |
| ----------------- | --------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------- |
| The quietest rung | [StatusPill](../components/status-pill.mdx)                           | <StatusBadge status="shipped" /> | Ambient state, in place, no interruption.                   |
| The middle rung   | [AlertBanner](../components/alert-banner.mdx)                         | <StatusBadge status="shipped" /> | An in-context statement the reader must see, with a budget. |
| The action        | [CareCard](../components/care-card.mdx)                               | <StatusBadge status="shipped" /> | What to do and by when, at three urgency levels.            |
| Not on the ladder | [Callout](../components/callout.mdx)                                  | <StatusBadge status="shipped" /> | Ordinary information, deliberately outside the status axis. |
| The top rung      | [Dialog](../components/dialog.mdx) · [Sheet](../components/sheet.mdx) | <StatusBadge status="shipped" /> | Interruption. Requires a rule, not a preference.            |
| The response      | [Button](../components/button.mdx)                                    | <StatusBadge status="shipped" /> | The action must be reachable from the alert itself.         |
| The boundary      | [DisclaimerNote](../components/disclaimer-note.mdx)                   | <StatusBadge status="shipped" /> | What this alert is and is not, where the rule requires it.  |

Tokens, by family:

| Family               | Where it applies                                                                         | Reference                                                            |
| -------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `--opsin-status-*`   | The pill, the banner and the care card, which are the only place the status axis appears | [Clinical status semantics](../health/clinical-status-semantics.mdx) |
| `--opsin-category-*` | Never on an alert. An alert grades; it does not identify                                 | [The two colour axes](../health/two-colour-axes.mdx)                 |
| Material rung        | The banner's rung in context; the interruptive surface's rung above it                   | [Layering and elevation](../foundations/layering-and-elevation.mdx)  |
| Motion               | Presentation only, never emphasis                                                        | [Motion in health UI](../health/motion-in-health-ui.mdx)             |

## Build it [#build-it]

<Steps>
  ### Decide whether this is an alert at all [#decide-whether-this-is-an-alert-at-all]

  One question: &#x2A;*does the reader have to do something?** If not, it is
  information, and information goes in a [Callout](../components/callout.mdx),
  which sits deliberately outside the status axis so that it cannot borrow urgency
  it has not earned.

  "Your results are ready" is information. "Your results are ready and your clinic
  needs you to call before Friday" is an alert. The difference is an action with a
  deadline, not the importance of the subject matter.

  ### Take the level from the clinical rule, not from the design [#take-the-level-from-the-clinical-rule-not-from-the-design]

  The four levels are steady, watch, attention and urgent. They are defined in
  [Clinical status semantics](../health/clinical-status-semantics.mdx), and they
  are assigned upstream of the UI. Your job is to render the level you were given,
  not to choose one that feels right.

  If you find yourself picking a level because the banner looked insufficiently
  serious, stop: you are making a clinical judgement in a stylesheet.

  ### Map the level to a rung, and no further [#map-the-level-to-a-rung-and-no-further]

  | Level     | Rung                          | Surface                                                                                                                                        |
  | --------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
  | steady    | ambient                       | [StatusPill](../components/status-pill.mdx) in place. No banner.                                                                               |
  | watch     | ambient                       | StatusPill, plus a sentence on the detail surface. Still no banner.                                                                            |
  | attention | in-context                    | [AlertBanner](../components/alert-banner.mdx) on the surface it concerns, with the action inside it.                                           |
  | urgent    | in-context, then interruptive | AlertBanner plus a [CareCard](../components/care-card.mdx) with the action and its timing. Interruption only if a documented rule requires it. |

  Note what is *not* here: no rung is a toast, and no rung is a notification badge
  on a tab. A message that appears and vanishes is not an escalation, it is a
  gamble that the person was looking.

  ### Spend the budget deliberately [#spend-the-budget-deliberately]

  One urgent surface per screen. That is the whole rule, and
  [Alarm fatigue](../health/alarm-fatigue.mdx) is the argument for it.

  When a second urgent message arrives for the same screen, you do not stack them.
  You either aggregate them into one surface that names both, or you demote one, or
  you move one to a screen of its own. Which of those it is, is a product
  decision that somebody makes once and writes down. There is no fourth option, and
  "show both" is not an option.

  Count *attention*-level surfaces too, with a soft ceiling of about two. Three
  amber banners is a screen the reader will learn to scroll past.

  ### Put the action inside the alert [#put-the-action-inside-the-alert]

  A banner that says something is wrong and gives no way to act on it is a source of
  anxiety with no outlet. Every alert at *attention* or above carries exactly one
  primary action, reachable from the alert itself, and that action is specific:
  "Call the clinic", "Log today's reading", "Book a repeat test". Not "Learn more".

  If the honest answer is that there is nothing to do yet, say that in words:
  "There is nothing you need to do. Your clinic will contact you." Then drop the
  alert a rung, because an alert with no action is information.

  ### Decide persistence and acknowledgement before you ship [#decide-persistence-and-acknowledgement-before-you-ship]

  Two symmetrical failures. An alert that cannot be dismissed becomes furniture and
  is stopped being seen within a week. An alert that vanishes on its own may never
  have been seen at all.

  Decide per level: what dismisses it, whether dismissal is recorded, whether it
  returns, and what condition ends it for good. The end condition is usually the
  underlying state resolving, not the person tapping something. An alert that a
  tap silences while the situation continues is the more dangerous of the two
  failures.

  ### Suppress repetition, not information [#suppress-repetition-not-information]

  The same alert re-raised daily is noise; a genuinely new instance is not. Suppress
  by identity and state, never by count. "We have already told them three times"
  is not a reason to stop telling them if the state is still true and still
  urgent. The right lever is to change the surface, not to go silent.
</Steps>

## The copy [#the-copy]

The sentence shape is fixed per level. The full patterns live in
[Writing status and alerts](../content/writing-status-and-alerts.mdx); the
essentials are:

* **State the fact first, in the reader's words.** Not the system's: "Your blood
  pressure reading was higher than usual", not "Threshold exceeded".
* **Then the action and its timing.** "Take another reading in the morning and log
  it." A deadline with no action, or an action with no deadline, is half a message.
* **Then the boundary, if the rule requires one.** What this is not: not a
  diagnosis, not a decision, not a substitute for advice.

<DoDont>
  <DoDont.Do>
    "Your blood pressure has been above your usual range for three mornings. Book
    a check with your clinic in the next week." That is fact, action, timing, in
    that order.
  </DoDont.Do>

  <DoDont.Dont>
    "⚠️ URGENT: ABNORMAL BP DETECTED!!" The faults are capitals and
    punctuation used as severity, a banned word, a machine's vocabulary, and no
    action at all.
  </DoDont.Dont>
</DoDont>

**Never use capitals, exclamation marks, sirens or emoji to carry severity.**
Severity is the status word, its icon and its surface. A design that needs
typographic shouting to feel urgent has an escalation problem, not a copy problem.

**Never frighten to drive compliance.** Fear works once and costs the reader's
trust permanently; a person who has learned that your app exaggerates will discount
the alert that matters.

**Emergencies are a special case and are not a banner.** If a value or an answer
implies immediate danger, the surface says plainly what to do right now: call
emergency services, or go to hospital. It comes in the shortest sentence that
can carry it, above everything else, and the rest of the screen gets out of the
way. The rule for when this applies belongs to your product and is not something
a component infers. See
[Clinical interaction guidelines](../health/clinical-interaction-guidelines.mdx).

## Get it right [#get-it-right]

**Safety**

* You can name the rule that assigned this level and who owns it.
* Exactly one urgent surface exists on the screen. Count them.
* The alert states a fact, then an action, then a boundary. Not a diagnosis.
* The action is reachable and specific.
* Dismissal does not resolve the underlying state, and does not pretend to.
* No alert is raised from a single out-of-range reading unless the rule says a
  single reading is sufficient. See
  [Reference ranges](../health/reference-ranges.mdx).
* The alert is honest about uncertainty: if the data behind it is stale or partial,
  the alert says so rather than asserting a current fact.
  [Uncertainty and staleness](../health/uncertainty-and-staleness.mdx) is the
  doctrine on stale and partial data.

**Accessibility**

* Severity is carried by the word and the icon as well as the colour, and survives
  greyscale and every simulated colour-vision deficiency.
  [Colour independence](../accessibility/colour-independence.mdx) sets that bar.
* The announcement is proportionate: an ambient status change is announced
  politely and does not interrupt; an interruptive surface takes focus, and only
  the interruptive surface may. The contract is in
  [Screen readers](../accessibility/screen-readers.mdx).
* Focus is never stolen at *attention* or below. A banner that grabs focus mid-task
  loses the reader's place and their typed input.
* An interruptive surface traps focus, is dismissible with <Kbd>Escape</Kbd>, and
  returns focus to where it came from. See
  [Keyboard and focus](../accessibility/keyboard-and-focus.mdx).
* Nothing pulses, flashes or shakes. Motion never conveys urgency, at any level,
  and under `prefers-reduced-motion` the alert is simply present.
  [Reduced motion](../accessibility/reduced-motion.mdx) covers that form.
* At 200% text the alert reflows and the action stays visible with it; the action
  never scrolls away from the sentence that justifies it.
* The dismiss control is a real control with a real name, at least 44pt, and is not
  the only way to leave the alert.

## Variations [#variations]

**The alert arrives while the app is closed.** A push notification is the same
message on a surface you do not control, delivered to a lock screen somebody else
may be able to read. It needs its own budget and its own privacy posture, and the
in-app alert the person finds on opening must match what the notification said.
A push that says more than the screen it opens is a broken promise.

<Todo>
  Off-screen alerts and their relationship to the in-app banner are not covered by
  any recipe or pattern yet. They are the largest real source of alarm fatigue in
  consumer health products.
</Todo>

**The alert is about the system, not the person.** A device that has stopped
syncing, a permission that has been revoked. Use a [Callout](../components/callout.mdx)
and keep it off the clinical status axis: the person is not in a clinical state
because your integration broke.

**Several alerts, same cause.** Aggregate at the cause, not the symptom. Five
missed readings is one message about a missed week.

**The alert is a question.** "Did you take your evening dose?" is not an alert; it
is a prompt for input, and it belongs in the logging flow.
[Capture a daily log entry](./daily-log-entry.mdx) is that flow.

**The reader is a carer, not the patient.** The urgency is the same, the wording is
not, and the privacy considerations are different. Say who the alert is about.

## Related [#related]

* **[Alarm fatigue](../health/alarm-fatigue.mdx)** is the doctrine behind the
  budget, including why prominence is a scarce resource rather than a free one.
* **[Alert escalation](../patterns/alert-escalation.mdx)** covers the same
  ladder at flow scale, from ambient to interruptive, with the per-session
  budget.
* **[AlertBanner](../components/alert-banner.mdx)*&#x2A; and
  &#x2A;*[CareCard](../components/care-card.mdx)** are the two component
  specifications that carry the top two rungs.
