Chip
The one labelled pill — 10 decorative hues, two tiers, an optional selection rung, and a real 24px remove control.
- Status
- Since
0.7.0- Accessibility pattern
- non-interactive label + remove button
Last updated
Install
Add Chip from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/chipThe same command installs the registry items it composes: @vegastack/icon-button.
Usage
import { Chip } from "@/components/ui/chip";
<Chip hue="blue" onRemove={() => removeLabel("API")} removeLabel="Remove API">
API
</Chip>;Chip is the primitive behind Tag, FilterChip, ChipInput's chips and ComboboxChip.
Reach for it directly when you need a labelled pill that none of those already is; reach for
the named component when one of them fits, so the page keeps its vocabulary.
Anatomy
Chip is a compound component. Every exported part, with the
data-slot it renders (generated from the canonical source):
Examples
Two tiers
sm (28px) is the inline tag tier — tags in a record row, chips inside a field. md (32px)
is the standalone control tier: it lines up with the Buttons, Inputs and Selects beside it,
which is why an applied filter in a FilterBar uses it.
The 10-hue palette
Chips ride the --tag-{hue} token trios (-subtle fill, -text ink, base accent) — 10
chromatic hues plus the neutral chip, each AA-gated in both themes by the token contrast
gate. Hue is a decorative label colour: it never signals status (that is Badge's five
semantic intents).
Removable
onRemove mounts the remove control: a round, ghost IconButton whose real border box is
24×24, so the WCAG 2.5.8 target is the button itself and no clipping ancestor can shrink it.
Always name what is being removed with removeLabel.
Selected
active promotes the neutral chip from its rest fill (surface-1) to the selection rung
(surface-2) — the same step every selected surface in the system takes. It is ignored for a
chromatic hue, whose tint already carries the meaning.
Composing another engine's chip
A chip whose behaviour belongs to another primitive keeps that primitive and wears this
geometry through Base UI render. That is exactly how ComboboxChip works:
<Chip size="sm" active render={<Combobox.Chip />}>
Design
<ChipRemove aria-label="Remove Design" render={<Combobox.ChipRemove />} />
</Chip>Chip vs Badge
Badge | Chip | |
|---|---|---|
| Voice | status (5 semantic intents) | label / selection (10 decorative hues) |
| Root | not interactive | not interactive |
| Extras | dot, loading, animate-in | remove control, selection rung |
API Reference
Chip
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | false | Marks the chip as an applied selection — the neutral chip takes the selection rung
(surface-2) instead of its rest fill. Ignored for chromatic hues, whose tint already
carries the meaning. |
children | React.ReactNode | — | The chip's content, laid out as a flex row. A part that can grow unboundedly is the
caller's to wrap: <span className="min-w-0 truncate"> — Chip cannot know which of
several children should give way. |
data-slot | string | 'chip' | Slot marker, for wrappers that compose Chip and want their own generated registry slot. |
hue | ChipHue | 'neutral' | Decorative hue from the tag palette. Never a status signal — that is Badge. |
onRemove | (() => void) | — | Render a remove affordance and call this when it is activated. The control is a real
24×24 IconButton, never a pseudo-element hit area. |
removeLabel | string | 'Remove' | Accessible name for the remove control. Always name what is being removed. |
render | useRender.RenderProp<Record<string, unknown>> | — | Replace the rendered <span> via Base UI render composition — how ComboboxChip
puts this geometry on Base UI's own Combobox.Chip. |
size | ChipSize | 'sm' | Tier. sm is the inline tag tier (28px); md is the standalone control tier (32px)
that lines up with Buttons and Inputs. |
ChipRemove
ChipRemove takes every IconButton prop; aria-label is required. Render it yourself only
when the remove control belongs to another engine — otherwise pass onRemove to Chip.
Accessibility
- The chip root is not interactive and has no hover or pressed state. Nothing happens when you click it, so nothing pretends otherwise.
- The remove control is a real 24×24
IconButton, so its pointer target is its own border box (WCAG 2.5.8) rather than an invisible pseudo-element a nested<button>would clip. removeLabelnames what is removed — “Remove SaaS”, not “Remove”.- Hue is decorative. Never let a chip colour be the only signal of status or meaning.
- Wrap a growable label in
<span className="min-w-0 truncate">so a long value ellipsises instead of stretching the row.
| Contract | States tested |
|---|---|
| Behaviour | default, active, removable, disabled |
| Accessibility | labeled, focus-visible |
| Visual | default, hover, pressed, selected, disabled |