Badge
When to use it
Section titled “When to use it”From the Lumo.Web.Component.V1.0.Complete library — theUse a badge to label a status or category the user reads, not clicks — a category tag, a status pill, a “Most popular” flag.
Badge description, read 2026-10-19.Where it goes
Section titled “Where it goes”- in a card header Action slot, and in a table cell or list row as a status
When not to use it
Section titled “When not to use it”- Don’t use it for something the user can interact with; use Chip for that.
- Use instead:
Chip— not implemented in this library yet.
- Use instead:
What each variant is for
Section titled “What each variant is for”| Property | Value | What it is for |
|---|---|---|
tone |
primary |
not stated in the source |
tone |
success |
not stated in the source |
tone |
warning |
not stated in the source |
tone |
danger |
not stated in the source |
tone |
neutral |
not stated in the source |
Accessibility
Section titled “Accessibility”- renders a <span>, so it is not focusable and carries no interactive role — the set defines no interaction state because a badge is read, not clicked
src/components/badge/badge.jsx:33
Composition
Section titled “Composition”Nothing in the stories shows this component composed with another.
What this version promises
Section titled “What this version promises”See Versioning. At 0.1.0 the public surface is the named exports of src/index.ts; this component is one of them.
This component’s release verdict is Cleared and its Development status reads Released. A Cleared verdict is a recommendation with reasons, recorded in its release review — a human decides whether a release happens, and no agent approves its own work.
Every story that is not a variant-matrix row, embedded live from the deployed Storybook in both themes. The matrix itself is on the Design tab.
Label — the box hugs it
Section titled “Label — the box hugs it”In context
Section titled “In context”Import
Section titled “Import”import { Badge } from '@ds-house/lumo-design-system';| Prop | Type | Default | Notes |
|---|---|---|---|
tone |
`‘primary’ | ‘success’ | ‘warning’ |
label |
string |
— | Figma Label. The text the badge shows. |
className |
string |
— |
Union types
Section titled “Union types”tone:'primary'|'success'|'warning'|'danger'|'neutral'
Tokens it needs
Section titled “Tokens it needs”| Token | Light | Dark |
|---|---|---|
--border-radius-chip |
not in the build | — |
--border-width-default |
not in the build | — |
--color-bg-negative-tonal-idle |
not in the build | — |
--color-bg-positive-tonal-idle |
not in the build | — |
--color-bg-primary-tonal-idle |
not in the build | — |
--color-bg-surfacesecondary |
not in the build | — |
--color-bg-warning-tonal-idle |
not in the build | — |
--color-text-brand |
not in the build | — |
--color-text-orange-twotone |
not in the build | — |
--color-text-positive |
not in the build | — |
--color-text-red-twotone |
not in the build | — |
--color-text-secondary |
not in the build | — |
--label-label1 |
not in the build | — |
--spacing-padding-sm |
not in the build | — |
--spacing-padding-xs |
not in the build | — |
A dash under Dark means the token does not change between themes.
Storybook’s own props table
Section titled “Storybook’s own props table”The Figma node
Section titled “The Figma node”The variant matrix
Section titled “The variant matrix”Each row as the set draws it, in both themes.
Primary
Section titled “Primary”Success
Section titled “Success”Warning
Section titled “Warning”Danger
Section titled “Danger”Neutral
Section titled “Neutral”All Tones
Section titled “All Tones”What Figma never bound, and the gaps recorded against it
Section titled “What Figma never bound, and the gaps recorded against it”1. Horizontal padding — resolved
Section titled “1. Horizontal padding — resolved”Originally the node’s horizontal padding was a raw 10px bound to no variable,
while the semantic scale had no 10 step (xs=4, sm=8, md=16, lg=24, xl=32; the
core scale jumps 8 → 12). Rather than author a token, the component was built
against spacing/padding/sm and the gap reported.
The designer has since bound the node’s paddingLeft/paddingRight to
spacing/padding/sm, which is the same token the component already used — so
code and design now agree at token level, not merely by eye. Read back from the
node to confirm.
Badge renders 60.67×24 against the node’s 61×24. The residual 0.33px is text rasterisation, not layout: Figma lays “Primary” out at 45px and the browser at 44.67. Padding, height and radius match exactly.
--lumo-badge-padding-inline in badge.css stays a named property — it is the
one measurement that had to be argued for rather than read off, and it is the
one whose value moves between platform modes.
2. Label contrast — resolved, in two passes
Section titled “2. Label contrast — resolved, in two passes”Pass one. Success and Warning bound an icon token to a text layer where the other three tones bound a text token — inconsistent, and the reason the Success badge was close to unreadable. Both were rebound to the text tier.
Pass two. That fixed Success but left Warning at 4.36 and Danger at 4.23,
both under AA. The remedy was not a darker shade authored, and not a shared
token retargeted. The system already has a matched twoTone tier — a
bg/<hue>/twoTone and a text/<hue>/twoTone per hue, intended to be used as a
pair — and Badge’s surfaces already were those backgrounds. Only the labels
were on the wrong tier.
| Tone | pass one | pass two | ratio |
|---|---|---|---|
| Success | icon/positive #16e362 → text/positive #17823e |
— | 1.61 → 4.57 |
| Warning | icon/warning #e37316 → text/warning #b15c16 |
→ text/orange/twoTone #824717 |
2.86 → 4.36 → 6.72 |
| Danger | — | text/negative #e31628 → text/red/twoTone #821720 |
4.23 → 8.92 |
The surfaces were already right, and not just coincidentally — they resolve to the same primitive, not merely the same hex:
| Badge binds | resolves to | twoTone pair | resolves to |
|---|---|---|---|
bg/warning/tonal/idle |
orange/100 #faf4ef |
bg/orange/twoTone |
orange/100 |
bg/negative/tonal/idle |
red/100 #faeff0 |
bg/red/twoTone |
red/100 |
Why the tier and not a retarget. The alternative was retargeting
color/text/negative to something darker. button.css consumes that token for
its error states, so it would have changed Button — which sits Completed with
a production link — and invalidated Button’s test evidence for a fix that had
nothing to do with Button. The twoTone route changed Badge and nothing else.
Naming. The generated custom properties lowercase the camelCase:
color/text/orange/twoTone emits as --color-text-orange-twotone. Verified in
build/css/ for web, admin and mobile rather than inferred from the Figma name.
Open question — should Badge be on twoTone throughout?
Section titled “Open question — should Badge be on twoTone throughout?”Not a gap, and not implemented: this is a visual change to tones that already pass, so it is a design decision nobody has made yet.
Badge now straddles two tiers. Warning and Danger are on twoTone; Primary,
Success and Neutral are on the general text tier. All five surfaces are already
<hue>/100, which is what bg/<hue>/twoTone resolves to, so the tier is in
effect half-adopted.
| Tone | today | on twoTone |
effect |
|---|---|---|---|
| Primary | text/brand #176e82 — 5.43 |
text/aqua/twoTone #164d5a — 8.68 |
visibly darker |
| Success | text/positive #17823e — 4.57 |
text/green/twoTone #17823e — 4.57 |
no visual change |
Success is the telling one: color/text/positive and color/text/green/twoTone
both resolve to green/800, so moving it is pure provenance — same pixels,
different tier. Primary is a real visual change, and the one that needs a
decision. Neutral has no twoTone counterpart at all: the tier covers aqua,
purple, blue, mint, green, yellow, orange, red and pink, with no neutral or grey,
so Neutral would stay on text/secondary regardless.
If the answer is yes, Badge ends up consistently on one tier and Primary gains a lot of contrast. If no, this section is the record of why it is mixed.
3. Pre-existing, repo-wide
Section titled “3. Pre-existing, repo-wide”The token build still warns that border-style-* and motion-easing-* have no
value in the export. Badge uses neither. Already recorded under docs/card.md.
Where Figma and this repo disagree across the system, see the disagreements record.
Every commit touching this component and its subcomponents — a change to a subcomponent changes the component, and a log that reads only the parent file misses it.
| Commit | Date | Shipped in | Subject |
|---|---|---|---|
2b94809 |
2026-10-19 | unknown | Write the ten component intent files |
c8bc699 |
2026-10-18 | unknown | Add the test suite, and retire the deliberate stop |
9525e0f |
2026-10-17 | unknown | Make the repo a publishable npm package |
9cad52f |
2026-10-12 | unknown | Move Badge’s Warning and Danger labels onto the twoTone tier |
ebcd8f6 |
2026-10-11 | unknown | Follow the Badge contrast rebind, and correct the stale notes |
54c65d4 |
2026-10-10 | unknown | Build Badge from Figma 17:1139 — five tones, every value on a token |
7ccfbbb53757.