Skip to main contentSkip to docs navigation

Chip Input

A multi-value text field that wraps typed entries into removable chips. Use it for tags, categories, email recipients, or any free-form list of values.

@layer components
Requires JavaScript
Depends onForm Input, Chip
Component tokens

Overview

.chip-input is an unstyled multi-value input container. Add .form-input alongside it to give it the standard text field appearance. Add data-cx-chips to enable JavaScript behavior — type a value and press Enter or , to create a chip. Click the close button or press Backspace on a focused chip to remove it.

The value of data-cx-chips sets the style classes applied to every chip created by this input — for example data-cx-chips="primary smooth" produces chips with the primary and smooth modifier classes. Omit a value or use default for the default chip appearance.

JavaScript TypeScript
HTML
<div class="form-input chip-input" data-cx-chips>
  <span class="chip" data-cx-chip-value="JavaScript">JavaScript</span>
  <span class="chip" data-cx-chip-value="TypeScript">TypeScript</span>
  <input type="text" class="ghost-input" placeholder="Add skill…" />
</div>

Chip styles

Pass any combination of chip modifier classes as the data-cx-chips value to control the appearance of chips created by this input. Pre-existing chips in the markup should carry the same classes as they will be created with.

Approved Verified
Bug Critical
Draft Review
HTML
<div class="form-input chip-input" data-cx-chips="primary">
  <span class="chip" data-cx-chip-value="Approved">Approved</span>
  <span class="chip" data-cx-chip-value="Verified">Verified</span>
  <input type="text" class="ghost-input" placeholder="Add status…" />
</div>
<div class="form-input chip-input" data-cx-chips="danger smooth">
  <span class="chip" data-cx-chip-value="Bug">Bug</span>
  <span class="chip" data-cx-chip-value="Critical">Critical</span>
  <input type="text" class="ghost-input" placeholder="Add issue…" />
</div>
<div class="form-input chip-input" data-cx-chips="default outline">
  <span class="chip" data-cx-chip-value="Draft">Draft</span>
  <span class="chip" data-cx-chip-value="Review">Review</span>
  <input type="text" class="ghost-input" placeholder="Add label…" />
</div>

Mixed chip styles

Pre-existing chips can carry individual style classes that differ from the data-cx-chips value. Only chips added at runtime will receive the classes set by data-cx-chips.

Low Medium High
HTML
<div class="form-input chip-input" data-cx-chips="primary smooth">
  <span class="chip default" data-cx-chip-value="Low">Low</span>
  <span class="chip warning smooth" data-cx-chip-value="Medium">Medium</span>
  <span class="chip danger smooth" data-cx-chip-value="High">High</span>
  <input type="text" class="ghost-input" placeholder="Add priority…" />
</div>

Empty state

Start with just the ghost input — chips are created as users type. Omit any pre-existing .chip elements.

HTML
<div class="form-input chip-input" data-cx-chips="primary smooth">
  <input type="text" class="ghost-input" placeholder="Type and press Enter…" />
</div>

With form field

Wrap the chip input in a form field to pair it with a label, help text, and validation feedback.

React CSS
Press Enter or , to add a skill.
HTML
<div class="form-field">
  <label class="form-label" for="skillsInput">Skills</label>
  <div class="form-input chip-input" data-cx-chips="primary smooth">
    <span class="chip" data-cx-chip-value="React">React</span>
    <span class="chip" data-cx-chip-value="CSS">CSS</span>
    <input type="text" class="ghost-input" placeholder="Add skill…" id="skillsInput" />
  </div>
  <div class="form-help">Press <kbd>Enter</kbd> or <kbd>,</kbd> to add a skill.</div>
</div>

Point the label's for attribute at the ghost input's id, not the container's id. The container does not need its own id.

Disabled

Apply the disabled attribute to the ghost input to prevent new chips from being added. Existing chips become non-interactive.

React CSS
HTML
<div class="form-input chip-input" data-cx-chips="primary smooth">
  <span class="chip" data-cx-chip-value="React">React</span>
  <span class="chip" data-cx-chip-value="CSS">CSS</span>
  <input type="text" class="ghost-input" placeholder="Disabled…" disabled />
</div>

Floating Labels

The chip input can be used within a floating label container just like a regular text input.

A Chip Another Chip
HTML
<div class="form-floating mb-medium">
  <label for="floatingInput">Add chips</label>
  <div class="chip-input form-input" data-cx-chips="primary smooth">
    <span class="chip" data-cx-chip-value="A Chip">A Chip</span>
    <span class="chip" data-cx-chip-value="Another Chip">Another Chip</span>
    <input type="text" class="ghost-input" id="floatingInput" />
  </div>
</div>

Usage

Via data attributes

Add data-cx-chips to a container element to automatically initialize chip input behavior. Pass chip style classes as the attribute value.

HTML
<div class="form-input chip-input" data-cx-chips="primary smooth">
  <input type="text" class="ghost-input" placeholder="Add tags…" />
</div>

Use additional data-cx-* attributes to configure the component:

AttributeDescription
data-cx-chipsInitializes chip input. The value sets the chip style classes ("primary smooth", "default", etc.).
data-cx-separatorCharacter that creates a new chip when typed. Default is ",". Set to null to disable.
data-cx-allow-duplicatesSet to "true" to allow the same value to be added more than once.
data-cx-max-chipsMaximum number of chips allowed. Omit for no limit.
data-cx-create-on-blurSet to "false" to prevent chip creation when the input loses focus.

Via JavaScript

Initialize manually and pass options as a configuration object:

JavaScript
const el = document.querySelector('.form-input.chip-input')
const chipInput = new ChipInput(el, {
  separator: ',',
  maxChips: 5,
  chipClass: 'primary smooth'
})

Options

NameTypeDefaultDescription
separatorstring | null','Character that triggers chip creation when typed. Set to null to disable.
allowDuplicatesbooleanfalseAllow duplicate chip values.
maxChipsnumber | nullnullMaximum number of chips allowed. null for no limit.
placeholderstring''Placeholder text for a dynamically created ghost input.
dismissiblebooleantrueAppend a close button to each created chip.
dismissTextstring'×'Text for the close button. Defaults to '×'.
createOnBlurbooleantrueCreate a chip from the current input value when focus leaves the field.
chipClassstring'default'Space-separated chip modifier classes applied to every chip. Overridden by the data-cx-chips attribute value.

Methods

MethodDescription
add(value)Creates a chip with the given value. Returns the chip element, or null if the value was rejected (empty, duplicate, or max reached).
remove(chipOrValue)Removes a chip by element reference or value string. Returns true on success.
removeSelected()Removes all currently selected chips and returns focus to the input.
getValues()Returns an array of all current chip values in order.
getSelectedValues()Returns an array of selected chip values.
clear()Removes all chips.
clearSelection()Deselects all chips without removing them.
selectChip(chip, options)Selects a chip element. Accepts { addToSelection, rangeSelect } options.
focus()Focuses the ghost input.
dispose()Destroys the component and removes all event listeners.
JavaScript
const el = document.querySelector('.form-input.chip-input')
const chipInput = ChipInput.getOrCreateInstance(el)

chipInput.add('JavaScript')
chipInput.add('TypeScript')

console.log(chipInput.getValues()) // ['JavaScript', 'TypeScript']

chipInput.remove('JavaScript')
chipInput.clear()

Events

EventDescription
add.cx.chip-inputFired before a chip is added. The event carries a value property. Call event.preventDefault() to cancel.
remove.cx.chip-inputFired before a chip is removed. The event carries value and chip properties. Call event.preventDefault() to cancel.
change.cx.chip-inputFired after any chip is added or removed. The event carries a values array with all current chip values.
select.cx.chip-inputFired when chip selection changes. The event carries a selected array of selected values.
JavaScript
const el = document.querySelector('.form-input.chip-input')

el.addEventListener('add.cx.chip-input', event => {
  if (event.value.length < 2) {
    event.preventDefault() // reject values shorter than 2 characters
  }
})

el.addEventListener('change.cx.chip-input', event => {
  console.log('Current values:', event.values)
})

Keyboard interaction

Chip input supports Mail.app-style keyboard navigation and multi-chip selection.

Input focused

KeyAction
EnterCreate a chip from the current input value.
, (or separator)Create a chip from the current input value.
Backspace / DeleteWhen the input is empty, select and focus the last chip.
When the cursor is at the start, move focus to the last chip.
Shift+Select the last chip and extend the selection.
EscapeClear selection, clear the input value, and blur.

Chip focused

KeyAction
Backspace / DeleteRemove all selected chips and focus the next closest chip or the input.
Move to the previous chip.
Move to the next chip, or to the input if already at the last chip.
Shift+ / Shift+Extend the selection in that direction.
HomeMove to the first chip.
EndMove focus to the input.
Cmd/Ctrl+ASelect all chips.
EscapeClear selection and return focus to the input.

Mouse and pointer

ActionEffect
Click chipSelect that chip (clears any previous selection).
Cmd/Ctrl+clickToggle the chip in or out of the current selection.
Shift+clickRange-select from the anchor chip to the clicked chip.
Click close buttonRemove the chip immediately.
Click container backgroundClear selection and focus the input.

CSS

The Chip input component can be customized at both runtime (via custom properties) and compile time (via Sass variables).

Custom properties

The Chip input component exposes CSS custom properties to control its appearance at runtime.

--padding-y: var(--chip-input-padding-y, #{$form-chip-input-padding-y});
--padding-x: var(--chip-input-padding-x, #{$form-chip-input-padding-x});
--gap: var(--chip-input-gap, #{$form-chip-input-gap});
--min-width: var(--chip-input-min-width, #{$form-chip-input-min-width});
--min-height: var(--chip-input-min-height, #{$form-chip-input-min-height});

Sass variables

The Chip input component uses Sass variables in scss/config/_defaults.scss to define its defaults; they are also exposed as CSS custom properties using the --cx- prefix for runtime override — $variable-name becomes --cx-variable-name. See the component anatomy page.

$chip-medium-font:                      $cx-font-chip-medium;
$chip-large-font:                       $cx-font-chip-large;
$chip-small-font:                       $cx-font-chip-small;

$chip-medium-min-size:                 $cx-size-chip-medium-main;
$chip-medium-padding-y:                 $cx-space-chip-medium-padding-y;
$chip-medium-padding-x:                 $cx-space-chip-medium-padding-x;
$chip-medium-gap:                       $cx-space-chip-medium-gap;
$chip-medium-nudge:                      $cx-space-chip-medium-nudge;

$chip-large-min-size:                  $cx-size-chip-large-main;
$chip-large-padding-y:                  $cx-space-chip-large-padding-y;
$chip-large-padding-x:                  $cx-space-chip-large-padding-x;
$chip-large-gap:                        $cx-space-chip-large-gap;
$chip-large-nudge:                       $cx-space-chip-large-nudge;

$chip-small-min-size:                  $cx-size-chip-small-main;
$chip-small-padding-y:                  $cx-space-chip-small-padding-y;
$chip-small-padding-x:                  $cx-space-chip-small-padding-x;
$chip-small-gap:                        $cx-space-chip-small-gap;
$chip-small-nudge:                       $cx-space-chip-small-nudge;

$chip-medium-icon-size:                 $cx-size-chip-medium-icon;
$chip-large-icon-size:                  $cx-size-chip-large-icon;
$chip-small-icon-size:                  $cx-size-chip-small-icon;

$chip-medium-avatar-size:               $cx-size-chip-medium-avatar;
$chip-large-avatar-size:                $cx-size-chip-large-avatar;
$chip-small-avatar-size:                $cx-size-chip-small-avatar;

$chip-border-width:                     $cx-border-width-chip-main;
$chip-medium-border-radius:             $cx-border-radius-chip-medium;
$chip-large-border-radius:              $cx-border-radius-chip-medium;
$chip-small-border-radius:              $cx-border-radius-chip-small;

$chip-remove-icon:                      $cx-icon-chip-remove;