Components · Data

Badge

A count, and nothing else. The rules that matter are not about rendering the number — they are about the number being exact, and about the set it is exact about, which is where badges actually mislead.

Stable · v1.0 shadcn/ui WCAG 2.2 AA SC 1.4.1

Overview

A badge is a count and nothing else. It never carries clinical state, never replaces a Status chip, and never becomes an alarm by turning red. A count of three tells you there are three things; it does not tell you any of them is critical, and it must not pretend to.

Every rule below is about the number being exact, or about the set it is exact about. The second is the harder one and the one this component gets wrong most often — see What the count counts. Where a set is larger than the screen, Pagination says which part of it you are looking at.

Anatomy, and the rule that carries the risk

Counts, and what sits next to them
  • Laboratory 6 1 critical
    Count and priority as two separate statements
  • Prior recordings 3
    A plain count, carrying no clinical claim
  • Imaging 0
    Zero is rendered, not hidden — "none ordered" is information
The rule that carries the risk

A badge never uses an --alarm-* colour to mean "some of these are serious". A red badge is an alarm signal borrowed for a quantity, and it does two kinds of damage: it dilutes the IEC 60601-1-8 palette everywhere else in the product, and it lets a critical finding be represented by a number nobody has to open. Where priority exists, say it in words with a status chip beside the count.

The single exception is documented on Colour and applies only to the alarm banner's own occurrence counter, where the surrounding component already carries the priority.

What the count counts

Every rule above is about the number being exact. None of them is about the number being exact about what, which is where badges actually mislead. A badge is a claim with a hidden subject, and three things quietly change that subject.

The rule

A badge counts what its label names, over the whole set. Where a filter, a permission or a time window narrows what the reader can actually reach, the badge either counts the whole set anyway or states the narrowing. It never silently counts the visible subset.

A filter is active

The same six results, under a filter hiding thirty-four

STATES THE NARROWING

Laboratory 6 shown · 34 hidden by filters 40

SILENTLY COUNTS THE SUBSET

Laboratory 6

The second badge is not wrong about anything it says. It is wrong about what it is about, and a clinician who set that filter twenty minutes ago has no way to tell. This is Filter's hidden-count rule arriving at a component that was designed without it in mind.

The reader cannot reach all of them

A count of forty results where the reader's role permits opening thirty-one is a count of things some of which do not exist for them. It reads as work they have not done. Either the badge counts what they can reach and says so, or it counts everything and states the difference — but the difference is stated, because Acting for someone else turns on a reader knowing what is being withheld and why.

A time window is implied

"6" beside Laboratory means six today, or six this admission, or six ever. The label carries the window or the badge does. Unscoped counts are read against whatever window the reader already had in mind, which is rarely the one the query used.

A count is a snapshot

A badge is only as current as its last fetch, and unlike a value in a Key–value pair it carries no timestamp of its own. On a worklist that polls every few minutes, a badge is a claim about the past presented in the present tense.

Rendering the number

States

StateRendering
CountedThe exact number, grouped by locale.
Zero Rendered as 0, never hidden. "None ordered" is information.
Unloaded or failed — with an accessible reason, never 0.
Narrowed by a filter The whole-set total, with what is hidden stated beside it — see What the count counts.
Stale The last known good value, with the surface stating that refreshing failed.
Updating Updates on data arrival without animation, and never reorders what it sits on.

What the component enforces, and what it only asks for

A badge is a number and a noun, and the type holds both. What it cannot hold is whether the number is right, which is the whole of what a badge is for.

RuleHeld by How
A count always names what it countsThe component label is required, in singular and plural forms, selected by Intl.PluralRules rather than by appending an s.
Asserted: Badge: the announced name agrees in number
A count is never cappedThe component There is no max prop, so “99+” cannot be produced. The difference between 99 and 340 unreviewed studies is a busy afternoon and a service failure.
Asserted: Badge: an exact count, never capped
It never becomes an alarmThe component tone offers neutral and attention only; no alarm hue is reachable.
Asserted: Badge: no alarm tone is reachable
It is not a controlThe component BadgeProps does not extend button or anchor props, so a 24 px badge cannot be made clickable beside the larger control it would be mistaken for.
Asserted: Badge: is not a control — no click or href surface to pass
A stale count shows its age on screenThe component asOf is visible beside the count as well as in its name. It was in the screen-reader text only, so a stale number was disclosed to one kind of reader and shown as current to everyone else.
Asserted: Badge: the age of the count reaches the accessible name
The noun agrees in the language it is written inThe component label takes every plural form a language has, and locale names the language, English by default. The rule was the browser’s, so English words were declined by French rules on a French workstation — “0 result” — and a language with more than two forms could not be written.
Asserted: Badge: plural forms follow the language of the words, not the browser
The number is correctYou It renders what you pass. A stale count is indistinguishable from a fresh one.
The set the number is about is the set the reader assumesYou hiddenByFilter exists so you can say a filter is deciding what the number means — but nothing makes you pass it.
It is currentYou asOf is optional and is a string you supply. A badge is only as current as your last fetch and carries no clock of its own.
Why this is written down

A rule stated on a component page reads as a rule the component keeps. 4 of these 7 are; 3 are obligations this page was quietly handing to the reader. Every mechanical rule is held and every rule about truth is yours, which is roughly the shape of this whole system.

Do's and don'ts

Do
Laboratory 6 1 critical

Quantity and priority stated separately. Six results; one of them is critical; both facts survive greyscale.

Don't
Laboratory 6

A red count. It borrows the IEC critical hue to mean "some of these matter", and dilutes it everywhere the product uses it properly.

Do
Imaging 0 none ordered
Pathology — count unavailable

Measured zero and unknown are different renderings. One is a fact; the other is a gap.

Don't
Imaging
Pathology 0

A hidden zero and a failed count rendered as zero. The clinician reads "nothing to see" twice, and is wrong once.

Do
Laboratory 6 shown · 34 hidden by filters 40

Counts the whole set and states what is narrowing the view. The clinician can tell that the six in front of them are not all of it.

Don't
Laboratory 6

Exact, and about the wrong set. A filter set twenty minutes ago is silently deciding what this number means, and nothing on screen says so.

Do

Worklist 12

Counts as of 14:26:38 · refresh failed at 14:31, showing the last successful count

The number kept its last real value and the surface admits it stopped updating. Both facts are needed to know what to do with it.

Don't

Worklist 12

Frozen at 14:26 and presented in the present tense. It has been wrong for five minutes and looks exactly as confident as it did when it was right.

Do

The whole row is the control, comfortably past the floor. The badge is content inside it.

Don't

Prior recordings

A 24 px control sitting beside the larger one it will be mistaken for, and below the target floor in gloves.

Accessibility

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.

Clinical safety notes

Risk controls this page specifies

Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). 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.

Implementation

$ npx shadcn@latest add https://md.notjustany.tech/r/badge.json
// A count and the thing being counted. No variant prop, because a badge
// has no clinical variants — and no max, so "99+" cannot be produced.
<Badge count={6} label="results" />

// A filter is narrowing the set: count stays the whole-set total.
<Badge count={40} label="results" hiddenByFilter={34} />

// Both plural forms, so the announced name agrees.
<Badge count={1} label={{ one: "result", other: "results" }} />
NotJustAnyMed.Tech Design System · Badge · v1.0 · draft for review
Reference applications named in this system are fictional; all patient data and reference ranges shown are fabricated and illustrative.