Chip
The one labelled pill — 10 decorative hues, two tiers, an optional selected fill, and a real 24px remove control.
- Status
- Since
0.7.0- Accessibility pattern
- non-interactive label + remove button
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Chip before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify chipInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/chipRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/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 and ChipInput's chips (ComboboxChip is not — see below).
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 a toolbar control sized to sit beside them 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 Button at
size="icon-xs", 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 moves the neutral chip from its rest fill (muted) to accent, the surface every
selection 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 can keep that primitive and wear this
geometry through Base UI render:
<Chip size="sm" active render={<Combobox.Chip />}>
Design
<ChipRemove aria-label="Remove Design" render={<Combobox.ChipRemove />} />
</Chip>ComboboxChip does not do this today: it is upstream's file, which renders Base UI's own
Combobox.Chip with its own classes, so a combobox chip does not share Chip's geometry.
Recomposing it on Chip is a known deviation, fixed when the file is next touched. Tag and
FilterChip are the two parts that compose Chip.
Chip vs Badge
Badge | Chip | |
|---|---|---|
| Voice | status (4 semantic families) | label / selection (10 decorative hues) |
| Root | not interactive | not interactive |
| Extras | nine variants, [a] hover | remove control, selected fill |
API Reference
Chip
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | false | Marks the chip as an applied selection — the neutral chip takes accent 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 Button size="icon-xs", 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 Button 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
Button size="icon-xs", 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 |