Components
Badge
The small status chip — a read-only label that carries a record's state: a row's status, a finding's severity, a flag in a detail hero. Officially it's a tinted chip — a soft fill of the tone, deep tone text, and a hairline border, with an optional status dot or severity icon. Easy to spot, easy to scan.
Five tones map straight onto the semantic ramp — neutral · info · affirm · caution · critical. The text label is always the signal, so color is reinforcement, not the message: a badge reads correctly in monochrome and for color-blind users (ADR-0017). Dark mode swaps the soft tints for dark surface tints, so the chip stays legible without changing its meaning.
Tones
Neutral is a plain gray chip — no severity, just a label. Info is the silver-blue of a state worth noting. Affirm is green, caution is bronze, critical is red — the same status colors the rest of the product speaks. Pick the tone for the meaning, never the color for the look.
Dot and icon
For severity tones, pair a non-colour cue so the meaning survives a greyscale print or a colour-blind reader (ADR-0017). A dot is the low-key nudge; a Lucide icon says it outright — the right call for caution and critical. The icon wins when both are set.
Solid
The one loud option — a chip filled with the full tone color and white type, the same recipe as a filled button. The fill is deep enough that contrast holds in both themes. Save it for the single status that has to dominate (a critical flag in a detail hero), not for table cells, where a whole column of them gets noisy fast.
In a table
A status column is where badges live — tinted chips with a dot make a friendly, scannable column. See it live in the /data-table status column.
Materiality
Everything resolves to tokens; the badge owns no raw values. No glass, no engraving — just a flat tinted chip that gets out of the way.
- tinted chip
- A soft fill of the tone with a hairline border in the same tint. The proposed system goes frameless here; the official badge is a friendly little chip instead.
- tone text
- The label paints from the tone ramp at the
-700step — deep color on the light tint, so it reads without squinting. Neutral rides the theme-aware--fg-2instead. - flat text
- Engraving belongs to the proposed system —
--engraving-shadowhas no job here. Official badge text is flat: solid color, no shadow, nothing to catch the light. - solid fill
- The solid emphasis fills with the
-700step and sets fixed near-white--ink-25type on top — clean white on color, like a filled button, in both themes.
Props
- tone?: BadgeTone
- 'neutral' | 'info' | 'affirm' | 'caution' | 'critical'. Defaults to 'neutral'.
- emphasis?: BadgeEmphasis
- 'plain' (default, tinted chip) | 'solid' (filled, white type). Reach for
emphasis="solid"only when one status must dominate. - dot?: boolean
- Renders a leading tone dot — a non-colour reinforcement for severity (ADR-0017).
- icon?: ReactNode
- A leading Lucide icon, inheriting the tone. Recommended for severity tones; takes precedence over
dot. - ...HTMLAttributes
- Renders a
<span>; remaining span attributes (className, title, data-*) pass through. Read-only by design — a badge is not interactive.
Usage
import { Badge } from '@halo-compliance/ui'
<Badge tone="affirm" dot>Active</Badge>import { Badge } from '@halo-compliance/ui'
import { TriangleAlert } from 'lucide-react'
<Badge tone="critical" icon={<TriangleAlert strokeWidth={1.75} />}>
Flagged
</Badge>const STATUS_TONE: Record<Status, BadgeTone> = {
Active: 'affirm',
Review: 'caution',
Inactive: 'neutral',
}
cell: ({ getValue }) => {
const status = getValue() as Status
return <Badge tone={STATUS_TONE[status]} dot>{status}</Badge>
}When to use
- A record's state. A status, a stage, a severity, a flag — anything you'd read off a row or a hero.
- Reinforced, not signalled, by colour. The label is the meaning; the tone (and dot / icon) reinforce it.
- Read-only. If it does something when clicked, it is a Button or a filter chip, not a badge.
Anti-patterns
- Colour as the only signal. A bare red dot with no label fails ADR-0017 — give it a word.
- Solid everywhere. A column of filled chips gets loud; tinted chips with a dot stay easy to scan.
- A clickable badge. If it removes a filter or opens a menu, that is a chip or a button — keep the badge inert.
- Tone for decoration. Pick the tone for the meaning; brand gold is for buttons, not statuses.
Related
- /data-table — the data grid whose status column these fill.
- /text-groups — the mono nameplate; the badge is its small status sibling.
- /helper-text — the severity message register the tones share (ADR-0017 icon pairing).
- /colors — the semantic ramp (
--affirm-700, etc.) the tones resolve to.