Skip to content
Component installs need the registry setup
VegaStack Design

Chip

The one labelled pill — 10 decorative hues, two tiers, an optional selected fill, and a real 24px remove control.

Status
stable
Since
0.7.0
Accessibility pattern
non-interactive label + remove button

Last updated

UncategorizedB2BSaaSEnterprise
Status: OpenOwner

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 chip

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/chip

Run 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):

Chip
ChipRemove — data-slot="chip-remove"

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.

Inline · smControl · md

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).

neutralbluecyangreenlimeyelloworangeredpinkmagentapurple

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.

Design partnerPriorityEMEA

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

BadgeChip
Voicestatus (4 semantic families)label / selection (10 decorative hues)
Rootnot interactivenot interactive
Extrasnine variants, [a] hoverremove control, selected fill

API Reference

Chip

PropTypeDefaultDescription
activebooleanfalseMarks 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.
childrenReact.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-slotstring'chip'Slot marker, for wrappers that compose Chip and want their own generated registry slot.
hueChipHue'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.
removeLabelstring'Remove'Accessible name for the remove control. Always name what is being removed.
renderuseRender.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.
sizeChipSize'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.
  • removeLabel names 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.
ContractStates tested
Behaviourdefault, active, removable, disabled
Accessibilitylabeled, focus-visible
Visualdefault, hover, pressed, selected, disabled

Do / Don't

Do
Use one Chip for every labelled pill, and name every remove control after what it removes.
Don't
Hand-roll a pill with its own height, radius and a sub-24px remove button.

On this page