Skip to content
Component installs need the registry setup
VegaStack Design

Chip Input

Free-token entry — Enter or comma commits, delimited pastes split into chips, and per-chip validation keeps invalid entries visible instead of dropping them.

Status
stable
Since
0.4.0
Accessibility pattern
labelled token input

Last updated

Tags
designtokensSome entries are invalid

Enter or comma commits; Backspace in the empty input removes the last chip.

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 Input before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify chip-input

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

pnpm dlx shadcn@4.21.0 add @vegastack/chip-input

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/input-group, @vegastack/tag-group, @vegastack/use-announcer.

Usage

import { ChipInput } from "@/components/ui/chip-input";

<ChipInput aria-label="Tags" value={tags} onValueChange={setTags} />;

ChipInput fills the gap neither existing component can: TagGroup is a display list with no input, and Combobox's value model is selection-from-items — it cannot commit an arbitrary token. The field chrome is InputGroup — border, focus border, invalid hairline, disabled wash, all of it imported — and the inner control is InputGroupInput, which is Input flattened into the box. The only thing this component adds to that chrome is a field that wraps: it grows with its chips instead of holding one 32px line. The chips are real Tags, 24px remove targets included.

Scope

BehaviourWhere it lives
Suggestions / autocompleteA Combobox composition — deliberately not built in
Persisting the listHost, via onValueChange
Chip colour semanticsNot here — Tag hue is decorative; invalid is the only state
+N overflow collapseTagGroup — display-list semantics, wrong for an editable field

Examples

Per-chip validation

Entries are added and marked, not silently dropped: a pasted list of twenty addresses with two typos shows twenty chips with two flagged (data-invalid, destructive border) — and the field stays invalid until they are fixed or removed.

Recipients
ada@example.comnot-an-email, invalid entrySome entries are invalid

Invalid entries stay visible and flagged — paste a list and fix the typos instead of losing them.

Inside a Field

Inside a Field the inner input is labelled by FieldLabel — a click on the label focuses it — described by the rendered FieldDescription and FieldError, and marked invalid by the Field or by an invalid chip. name submits every chip with a form, one value per chip.

sofaSome entries are invalid

Search matches any of these words.

Maximum entries

max caps the number of chips. An entry past the cap is not added, stays in the input, and the reason is announced and shown under the field until the next keystroke — "Up to 3 entries" by default, or your own copy from maxLabel. A skipped duplicate and an invalid entry show the same way.

redgreenblueSome entries are invalid

Up to 3 colours.

States

Empty, filled and disabled; the invalid field is the per-chip validation example above. Click or Tab into a field to see the focus state: the box's border darkens subtly and nothing fills; an invalid field keeps its destructive border while focused.

Some entries are invalid
events.createevents.deleteSome entries are invalid
lockedSome entries are invalid

API Reference

PropTypeDefaultDescription
allowDuplicatesbooleanfalseAllow the same chip twice. When false, a duplicate entry is rejected and announced.
aria-labelstring—Accessible name for the inner input. Inside a Field the FieldLabel names it; standalone, the control must never be unnamed.
classNamestring—Extra classes for the root column (data-slot="chip-input-root") that holds the box and its refusal line — where layout classes (flex-1, col-span-*, a min width) belong.
defaultValuestring[][]Initial chips for uncontrolled use.
disabledbooleanfalseDisables the field and every chip's remove button.
inputRefReact.Ref<HTMLInputElement>—Ref forwarded to the inner <input>.
maxnumber—The most chips the field accepts. At the cap, further entries are not added and the reason is announced.
maxLabel((max: number) => string)(max) => `Up to ${max} entries`The copy announced when an entry is refused at max.
namestring—Submits every chip with a surrounding <form> under this name — one value per chip, the way a multi-select posts.
normalize((raw: string) => string)(raw) => raw.trim()Normalise a raw entry before it is committed (and before duplicate checking). The default trims whitespace.
onValueChange((value: string[]) => void)—Fired with the next chip list on every add or remove.
placeholderstring—Placeholder for the inner input while the field has room.
refReact.Ref<HTMLDivElement>—Ref forwarded to the field box <div> (data-slot="chip-input"), which carries the data-invalid and data-disabled state.
splitOnRegExp/[,\n]/Pattern the paste handler splits on, in addition to the Enter/comma commit keys.
validate((chip: string) => boolean)—Per-chip validity. Invalid entries are still **added**, marked with data-invalid on their chip, described as invalid for assistive tech, and flip the whole field invalid until fixed or removed — a pasted list keeps every entry visible instead of silently dropping the malformed ones.
valuestring[]—Controlled chip list. Pair with onValueChange; omit for uncontrolled use.

Data attributes and CSS variables on ChipInput

AttributeValues
data-disabled""
data-field-group""
data-invalid""
data-slot"chip-input" | "chip-input-notice" | "chip-input-root"

Accessibility

  • The inner input must carry an accessible name (aria-label) — it is the control assistive tech interacts with.
  • Accepted, invalid, duplicate, and removed outcomes are announced through a polite live region; invalid chips also carry a text description, never colour alone. Refusals and invalid entries also show as one line under the field (hidden from assistive tech, which already hears the announcement), cleared by the next keystroke.
  • Every chip's remove button is named (Remove …) with a 24px hit area (from Tag).
  • The field is a field group: while the input inside holds focus its border darkens subtly (border-ring/50, easing in over 150ms) and no background changes — text entry's exception to FOC-14 (MK 2026-09-27). An invalid field keeps its destructive border in every state; focus never replaces it. This matches the Combobox chips field.
KeyAction
Enter / ,Commit the draft as a chip.
Backspace (empty input)Remove the last chip.
TabMove between the input and chip remove buttons.
ContractStates tested
Behaviourempty, chips, invalid-chip, duplicate-rejected, disabled
Accessibilitykeyboard, labeled, status-announcement, semantic-html
Visualdefault, focus, invalid, disabled, dark

Do / Don't

Do
Validate per chip and let malformed entries stay visible and flagged — the user pasted them for a reason.
Don't
Reject invalid entries silently on commit — a dropped address in a 20-recipient paste is data loss the user won't notice.

On this page