affectUI › Components › Inputs

Tag Input

Several values in one field as chips – paste a whole list, a check per value, duplicates flash, Backspace and arrow keys to edit.

Free and open source (MIT). Install: npx shadcn@latest add https://www.affectui.com/r/tag-input.json · Import from @/components/ui/tag-input

When to use

  • Several short values in one field: email addresses to invite, labels, keywords, domains, stop words
  • Lists people paste from elsewhere – commas, spaces or new lines turn into chips at once

When not to use

  • Picking from a fixed list – use the Combobox, which suggests as you type
  • A single value – use the Text Field

Usage

import { useState } from "react";
import { TagInput } from "@/components/ui/tag-input";

const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

export function InviteField() {
  const [emails, setEmails] = useState<string[]>([]);

  return (
    <TagInput
      label="Invite people"
      value={emails}
      onChange={setEmails}
      placeholder="name@company.com"
      avatar
      splitOnSpace
      max={10}
      normalize={(t) => t.trim().toLowerCase()}
      validate={(t) => (EMAIL.test(t) ? true : `“${t}” doesn't look like an email address.`)}
      name="emails"
    />
  );
}

Props

NameTypeDefaultDescription
valuestring[]The chips – makes it controlled
defaultValuestring[][]Starting chips when uncontrolled
onChange(tags: string[]) => voidCalled when a chip is added or removed
validate(tag: string) => true | stringChecks a value before it becomes a chip; a message keeps it in the field and shows under it
normalize(tag: string) => stringtrimCleans a value first, e.g. lowercases an email
splitOnSpacebooleanfalseAlso split on spaces – right for emails and single words, wrong for labels with spaces
maxnumberAt most this many chips; shows "3/10" under the field
avatarbooleanfalseA small circle with the first letter on every chip, for people
labelReactNodeShown above the field and tied to its input
hintReactNodeA quiet line under the field – a message takes its place while there is one
errorReactNodeMarks the field invalid from outside and shows the message
optionalbooleanfalseAdds "Optional" next to the label
placeholderstring"Add…"Shown while there are no chips; "Add more…" after the first
namestringSends the chips with a form, one hidden input per chip
disabledbooleanfalseDims the field and ignores input

Keyboard

  • Enter / ,: Turns what you typed into a chip (Tab does too, then moves on)
  • Space: Also adds, with splitOnSpace
  • ⌫: On an empty field: the first press marks the last chip, the second removes it
  • ← / →: Walk the chips; Backspace or Delete removes the one in focus
  • Esc: Empties what you were typing

Accessibility

  • A real text input with a label; the chips sit before it, each with a labelled Remove button
  • Added and removed values are announced in a polite live region; a message sets aria-invalid on the input
  • Chips are reachable with ← from the start of the field, so Tab still moves straight on to the next control

Responsive

  • Chips wrap onto new lines; after about five lines the field scrolls inside
  • Long values end in … with the full text on hover
  • Touch screens get taller chips, a bigger × and 16px text; dark mode built in via .dark or data-theme="dark"

Requirements

  • React 19

Related

  • Text Field – One input for every form – label, hint and error in place, prefix and suffix, clear, show-password and copy buttons, a character count
  • Combobox – Pick one or many from a searchable, grouped list – chips with avatars, highlighted matches, create-on-the-fly and full keyboard support
  • Select – Pick one option in the Text Field's look – icons and a second line per option, a gliding highlight, type-ahead and a list that never gets cut off
  • Button – Primary, secondary, ghost and destructive buttons with icons, sizes and a shimmering loading state