Skip to main contentSkip to docs navigation

Chip Input

Multi-value text field that wraps typed entries into removable, keyboard-navigable chips.

Overview

ChipInput combines a text field with a focusable, removable list of chips — type a value and press Enter or the separator character (, by default) to turn it into a chip. Arrow keys move focus between chips, and Backspace/Delete removes a focused (or selected) chip. Each chip renders as a Chip internally, so chipVariant accepts the same color/variant class names Chip documents.

React
TypeScript
JSX
import { ChipInput } from '@chassis-ui/react'

export const Example = () => {
  return (
    <ChipInput
      aria-label="Skills"
      defaultValue={['React', 'TypeScript']}
      placeholder="Add skill…"
      chipVariant="primary smooth"
    />
  )
}

As a form field

Pass label/help to wrap the input in a .form-field with an associated FormLabel and FormHelp. Pass name to have ChipInput create a hidden <input type="hidden"> per chip — all sharing that name, so a native form submission collects them together (FormData.getAll('skills')).

With required, a chip input with no chips holds its form back from submitting, and its text input takes focus. Text typed there and not added as a chip is not a value. The field then shows as invalid, with its invalidFeedback, until it has a value or the form is reset; set invalid yourself, to false too, to decide how it looks instead.

React
CSS
Press Enter or , to add a skill.
JSX
import { ChipInput } from '@chassis-ui/react'

export const Example = () => {
  return (
    <ChipInput
      label="Skills"
      help="Press Enter or , to add a skill."
      defaultValue={['React', 'CSS']}
      name="skills"
      placeholder="Add skill…"
    />
  )
}

Chip styles

Pass chipVariant with any combination of chassis-css chip modifier classes (e.g. "primary smooth", "danger") to style every chip this input creates.

Approved
Verified
Bug
Critical
JSX
import { ChipInput } from '@chassis-ui/react'

export const Example = () => {
  return (
    <div className="vstack gap-md">
      <ChipInput
        aria-label="Status"
        chipVariant="primary"
        defaultValue={['Approved', 'Verified']}
        placeholder="Add status…"
      />
      <ChipInput
        aria-label="Issue labels"
        chipVariant="danger smooth"
        defaultValue={['Bug', 'Critical']}
        placeholder="Add label…"
      />
    </div>
  )
}

Controlled values

Pass value/onChange to control the set of chip values yourself. Omit them (use defaultValue instead) to let ChipInput manage its own state.

React
Current values: React
JSX
import { useState } from 'react'
import { ChipInput } from '@chassis-ui/react'

export const Example = () => {
  const [values, setValues] = useState<string[]>(['React'])

  return (
    <div className="vstack gap-sm">
      <ChipInput aria-label="Skills" onChange={setValues} placeholder="Add skill…" value={values} />
      <div className="form-help">Current values: {values.join(', ') || '(none)'}</div>
    </div>
  )
}

Disabled

Add disabled to make ChipInput non-interactive — existing chips stay visible but can't be focused, removed, or added to.

React
CSS
JSX
import { ChipInput } from '@chassis-ui/react'

export const Example = () => {
  return <ChipInput aria-label="Skills" defaultValue={['React', 'CSS']} disabled />
}

Keyboard interaction

  • Input focused — Enter or the separator character creates a chip from the current value. Backspace/Delete on an empty input, or ← with the cursor at the start, selects and focuses the last chip. Escape clears the input and blurs.
  • Chip focused — ←/→ move between chips. Shift+←/→ extends the selection. Cmd/Ctrl+A selects all chips. Backspace/Delete removes the selected chip(s). Escape clears selection.

Accessibility

ChipInput needs an accessible name for the chip group as a whole — pass aria-label, aria-labelledby, or label (see As a form field). Without one, only the text input itself falls back to a generic label, leaving the chip list unidentified to screen reader users.

Scope

Built on React Aria's useTagGroup/useTag for the chip collection (arrow-key navigation, multi-select, removal) plus useTextField for entry — there's no single dedicated "chip input" hook, so the two are composed together with hand-written logic for separator/paste-splitting, matching the plan's own framing of this as the most bespoke of the newer form widgets.

A few Mail.app-style affordances from chassis-css's vanilla chip-input.js aren't wired up here: shift+click range selection and cmd/ctrl+click toggle selection with the mouse, and automatically moving focus back to the input when → or End is pressed on the last chip. Keyboard chip-to-chip navigation, selection, and removal all work; only these secondary mouse/edge-of-list conveniences are out of scope for this pass.

API

ChipInput

PropTypeDefault
allowDuplicates?booleanfalse
Allow the same value to be added more than once. Defaults to false.
aria-label?string—
An accessible label for the chip group, used when there's no visible <label>.
aria-labelledby?string—
Identifies a visible <label> element for the chip group.
chipVariant?stringdefault
Space-separated chassis-css chip modifier classes (e.g. "primary smooth") applied to every chip. Defaults to "default".
className?string—
A string of all className you want applied to the base component.
defaultValue?string[]—
The initial set of chip values (uncontrolled).
disabled?boolean—
Prevents new chips from being added and makes existing chips non-interactive.
help?ReactNode—
A description for the field, rendered below the chips.
id?string—
id forwarded to the text input — useful for pairing with a <label for>.
invalid?boolean—
Set component validation state to invalid. Left unset, the component shows as invalid by itself from a submit it held back (an empty required one, say) until it no longer would, or its form is reset. Set it, to false too, and it alone decides.
invalidFeedback?ReactNode—
An error message for the field, rendered below the chips when invalid is set, and after a submit the field held back, until it no longer would.
label?ReactNode—
The field's caption, rendered as a FormLabel associated with the text input.
maxChips?number—
Maximum number of chips allowed. Omit for no limit.
name?string—
name of an auto-created hidden input per chip, kept in sync with the values, for native form submission. Omit to skip creating them.
onChange?((values: string[]) => void)—
Callback fired whenever a chip is added or removed.
placeholder?string—
Placeholder shown in the input when empty.
required?boolean—
Toggle the required state for the component. A required chip input with no chips holds its form back from submitting, and its text input takes focus: text typed there and not added as a chip is not a value.
separator?string | null,
Character that creates a new chip when typed, or pasted text is split on. Set to null to disable. Defaults to ,.
size?"sm" | "lg"—
Size the component sm or lg.
valid?boolean—
Set component validation state to valid.
validFeedback?ReactNode—
A success message for the field, rendered below the chips when valid is set.
value?string[]—
The set of chip values (controlled).