Components
Chips
The friendly little label. Identity, not state — tags, taxonomy entries, references. Officially a chip is a flat pill: eggshell fill, warm hairline, type that stays out of the way. It names things without judging them, and a row of them scans easily on any record.
Chip is Beta. It covers three usages: a static identity label, a removable input chip (a user-applied tag with an X affordance), and a selectable filter / choice chip (a toggle with a check glyph). For a status that judges a record, reach for Badge; for a full multi-select field with a menu, reach for Selector.
Usages
One component, three jobs. Static chips have no handlers and stay out of the tab order. Removable chips carry their own remove button. Selectable chips render the label as a toggle button with aria-pressed — selected gets the gold treatment AND a check glyph, so it never rests on color alone (ADR-0017).
In dense context
The boundary-readability check: chips in running text must parse as discrete labels, not inline phrases. The eggshell fill against the white surface keeps each pill reading as its own little thing.
Selection — filter and choice
Selected chips wear the portal’s active grammar, the same one you see on the current nav item: the gold wash with gold text — gold as active state, never as a container frame. A check glyph rides along too, like Checkbox pairs its mark — color does the talking, the glyph makes it stick (ADR-0017). A filter set toggles independently. A choice set is pick-one: the same control, with the caller clearing siblings in its own state.
Removable — input chips
When a chip represents user-applied state (a manual tag, an attached reference), pass onRemove for the trailing X. It is a real button — keyboard reachable, with its own accessible name. The name defaults to a translated "Remove {label}" when the label is a plain string; pass removeLabel explicitly whenever it is not. Static chips (system taxonomy, immutable identifiers) omit the remove.
Materiality
The spec values below document the proposed system’s ink-on-plate treatment. The official chip plays it differently: a flat opaque pill, eggshell fill behind a warm hairline, and the gold active wash with gold text when selected. Friendly, never fussy.
- form
--radius-smcorners,--space-px-2×--space-2padding.A full pill — round ends, soft and friendly. The pill shape is what says "label" against the squarer cards and buttons.
- surface
--ink-100fill behind a--ink-300hairline (--ink-800/--ink-600on the dark plate).Eggshell on the white surface — a quiet fill behind the warm hairline. Gold never frames a container; the border stays neutral until selection.
- selected
- Frame darkens to
--fg-1; a--space-3check glyph appears.The portal’s active grammar: the gold-50 wash, gold-700 text, a touch more weight — plus the check glyph, so color-blind users get the state too (ADR-0017).
- remove
--space-4hit box,--space-3Lucide X at 1.75px stroke,--fg-3rising to--fg-1on hover.An ink affordance, not a metal button — quiet until pointed at.
- motion
- Frame and color settle over
--dur-quickwith--ease-mechanical.A little lift on hover, a little press on click — the fun register. Reduced-motion turns it all off.
Props
- children: ReactNode
- The label. Keep it nominal — chips name things, they don’t judge them.
- selected?: boolean
- Selected state for filter / choice usage. Only meaningful with onSelectedChange; the selected chip draws the check glyph.
- onSelectedChange?: (selected: boolean) => void
- Makes the chip selectable: the label becomes a toggle button with aria-pressed, fired with the next selected state.
- onRemove?: () => void
- Renders the trailing remove button (input usage). A separate tab stop — selection and removal never nest.
- removeLabel?: string
- Accessible name for the remove button. Defaults to a translated "Remove {label}" when children is a plain string, else "Remove" — pass it explicitly whenever the label isn’t a string.
- disabled?: boolean
- Dims the chip to 50% and blocks both the toggle and the remove.
- id, title, …
- Standard span attributes pass through to the chip’s root element.
Usage
import { Chip } from '@halo-compliance/ui'
<Chip>Q2 audit</Chip>
<Chip>NMLS-23456</Chip>{tags.map((tag) => (
<Chip
key={tag}
onRemove={() => setTags((prev) => prev.filter((x) => x !== tag))}
>
{tag}
</Chip>
))}// Filter — independent toggles
<Chip selected={utah} onSelectedChange={setUtah}>Utah</Chip>
// Choice — pick-one; the caller clears siblings
{appetites.map((a) => (
<Chip
key={a}
selected={appetite === a}
onSelectedChange={() => setAppetite(a)}
>
{a}
</Chip>
))}When to use
- Naming things on a record — tags, taxonomy entries, references; identifiers that label without judging.
- Editable sets — user-applied tags or attached references the user can take back (the removable variant).
- Compact filtering or pick-one above a list — a short row of toggles where a Selector menu or a checklist would be heavier than the job.
Antipatterns
- Status in a chip. A chip never judges. "Flagged", "Active", severity of any kind — that is a Badge, with its semantic tone and icon pairing.
- A chip as an action. If clicking navigates or commits something, it is a Button or a Link. The chip’s only verbs are select and remove.
- Long pick-one lists as choice chips. A dozen chips is a wall; pick-one beyond a handful of options is a Selector.
- A non-string label without removeLabel. The default accessible name can only be derived from a plain-string label — anything richer needs an explicit removeLabel.
Related
- /badge — the status sibling: semantic tones, icon pairing, judgment.
- /selector — the full multi-select field; its selected values render as removable chips.
- /checkbox — the same selection semantics (aria-pressed, glyph paired with the state).
- /tokens —
--radius-sm,--ink-100, and the duration / easing tokens.