Badge
Status badges for approval states and document statuses. The bare
.bo-badge is the neutral tone. Foregrounds use the
--bo-color-*-text tokens, which remap in dark mode to stay AA.
A badge is decoration for sighted users — the status text itself is the
accessible signal, so never encode status as color alone.
Not for anything the reader can act on — a badge is a
label, and a label that responds to a click is a control wearing the
wrong clothes. A filter value the reader can remove is a
chip; anything that fires an
action is a button. The test
is whether removing it would change what the screen does: if yes, it was
never a badge.
Tones
View code
<span class="bo-badge">Neutral</span>
<span class="bo-badge bo-badge--success">Approved</span>
<span class="bo-badge bo-badge--warning">Pending</span>
<span class="bo-badge bo-badge--danger">Rejected</span>
<span class="bo-badge bo-badge--accent">In review</span> --type — record-type chip
An inverted-surface tag naming the record a card holds. A rectangle, not a pill; auto-inverts in dark mode.
View code
<span class="bo-badge bo-badge--type">Purchase Order #4021</span>
<span class="bo-badge bo-badge--type">Invoice</span>
<span class="bo-badge bo-badge--type">Change Request</span> In a table — the number-one placement
| Invoice # | Amount | Status |
|---|---|---|
| INV-10234 | $4,208.00 | Pending |
| INV-10235 | $18,940.50 | Approved |
| INV-10236 | $730.25 | Rejected |
A status column of badges next to numeric cells — where badges actually live in an ERP. The tone word IS the text, so the meaning never depends on the hue alone.
Ranked values — priority, severity, risk,
ABC class — need no extra channel. Tone says bad, not
how much, so the obvious-looking move is an ordinal modifier
(--priority, data-rank). It is refused, and the
reason is on this page already: the word is the text. "High" and "Low" are
themselves the ordinal channel — readable, sortable, translatable, and
announced — and the two-channel rule means that word has to be there whatever
else you add. A rank glyph would encode the same fact a second time.
What would change this: a screen where the user must compare
rank across many rows at a glance and reading the words is too slow.
Measured across the 27-screen ERP suite, that screen does not exist — 168
badges, none of them ranked (roadmap 151.3).
Quick reference generated
| Class | Role |
|---|---|
| .bo-badge | block (root) |
| .bo-badge--accent | variant: accent |
| .bo-badge--danger | variant: danger |
| .bo-badge--success | variant: success |
| .bo-badge--type | variant: type |
| .bo-badge--warning | variant: warning |
API generated from CSS
| Blocks | bo-badge |
|---|---|
| Variants | bo-badge--accentbo-badge--dangerbo-badge--successbo-badge--typebo-badge--warning |
| Component tokens | --bo-badge-bg--bo-badge-border--bo-badge-fg |
| JS required | None — CSS-only. |
- A badge is decoration — the status TEXT is the accessible signal; never encode status as color alone.
Contrast — 33 AA pairs this component uses (generated, CI-gated)
| Text | on | Light | Dark |
|---|---|---|---|
| text-primary | bg-surface | 17.74 | 16.15 |
| text-primary | bg-canvas | 16.98 | 18.08 |
| text-secondary | bg-surface | 10.31 | 11.46 |
| text-secondary | bg-muted | 9.37 | 9.75 |
| text-muted | bg-surface | 7.56 | 6.65 |
| text-muted | bg-muted | 6.87 | 5.66 |
| bg-surface | text-primary | 17.74 | 16.15 |
| accent-text | accent-subtle | 7.27 | 8.38 |
| danger-text | danger-subtle | 5.91 | 8.06 |
| warning-text | warning-subtle | 4.84 | 9.21 |
| success-text | success-subtle | 4.79 | 10.17 |
| danger-text | bg-surface | 6.47 | 8.89 |
| danger-text | bg-canvas | 6.19 | 9.96 |
| success-text | bg-surface | 5.02 | 12.02 |
| success-text | bg-canvas | 4.8 | 13.46 |
| accent | bg-surface | 5.47 | 9.07 |
| text-primary | bg-hover | 16.12 | 13.75 |
| text-primary | bg-selected | 17.01 | 11.86 |
| text-secondary | bg-selected | 9.88 | 8.41 |
| accent-text | bg-selected | 7.27 | 8.38 |
| text-primary | bg-surface-raised | 17.74 | 14.51 |
| text-secondary | bg-canvas | 9.86 | 12.82 |
| border-control | bg-surface | 4.83 | 6.65 |
| --bo-state-error-color | bg-surface | 4.83 | 6.1 |
| accent | accent-subtle | 5.25 | 6.66 |
| border-control | bg-muted | 4.39 | 5.66 |
| accent | bg-muted | 4.97 | 7.72 |
| warning-strong | bg-muted | 4.56 | 6.69 |
| warning-strong | bg-surface | 5.02 | 7.86 |
| danger | bg-muted | 4.39 | 5.19 |
| text-primary | bg-muted | 16.12 | 13.75 |
| success-text | bg-muted | 4.56 | 10.23 |
| danger-text | bg-muted | 5.88 | 7.57 |
Maturity generated
- Introduced
- v0.1.0
- CSS floor
- Chrome/Edge 99 · Firefox 97 · Safari 15.4
- Alignment scored
- 2026-08-23
- Assistive-tech evidence
- None recorded
Pin this component's first release with npm i @busy-office/ui@0.1.0.
The floor is derived from this component's stylesheet alone — set by
@layer — so it sits at or below the
whole framework's Chrome/Edge 119 · Firefox 129 · Safari 17.5.
No assistive-technology runtime result is recorded for any component: it needs a human listening to VoiceOver and NVDA on owner hardware; no gate in this repo can simulate announcement behaviour.
What automation covers instead is test:axe across every page at 1440 and 390, and check:forced-colors — see
accessibility.
Design-system alignment generated
100% (15/15) — how closely this component follows the design language. Each rule is scored 0-3 against a checkable citation; rules that don't apply leave the denominator. Scored 2026-08-23.
| Rule | Score | Evidence |
|---|---|---|
| Typography | 3 / 3 | --bo-font-size-xs and a weight token; the 0.05em letter-spacing is intrinsic, same basis as form/combobox |
| Colour | 3 / 3 | raw hex only inside @media print, where theme tokens would be wrong on paper; status meaning is carried by TEXT, colour never alone |
| Spacing | 3 / 3 | the chip hug and the unconditional 1px boundary both carry their reason; the bare numbers a scan flags are measurements quoted INSIDE the WRAP comment on .bo-badge, not declarations. Corrected 2026-09-04 (Polish round 1 on avatar): this cite used to point at a line number in badge.css, which 249.8's 3-line @tagline header moved underneath it; it names the comment instead, which does not move |
| Interaction | n/a | a static span |
| Content | 3 / 3 | opener carries the wrong-choice clause: "Not for anything the reader can act on" — a badge is a label; a removable filter value is a chip and anything that fires is a button, with the test stated (would removing it change what the screen does). Blind re-scored 2026-08-23 (Polish round 2), judged genuine: clickable/dismissible badges are a common misuse of a status pill. |
| Fit | 3 / 3 | approval states and document statuses; the --type rectangle is scoped to record-type labels |