WebberUI

Tag Input

A tag input — Enter or comma adds a pill chip (trimmed and deduplicated), Backspace on an empty input deletes the last one; chips spring in, and the rest close ranks with FLIP on removal, with no accidental adds mid-IME-composition.

Loading preview…
npx shadcn@latest add https://webberui.com/r/tag-input.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

6
<TagInput />

Installation

npx shadcn@latest add https://webberui.com/r/tag-input.json

Or, once registries are configured in components.json, install it as @webberui/tag-input.

Usage

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

<TagInput
  value={["Design", "Animation"]}
  onChange={(tags) => console.log(tags)}
  maxTags={8}
/>

When value is omitted, the component manages the state internally (uncontrolled mode); passing value together with onChange puts it in controlled mode. onChange receives the latest string[] on both additions and removals.

Props

PropTypeDefaultDescription
valuestring[]Controlled tag array; when omitted, the component manages it internally
onChange(tags: string[]) => voidFires when the tags change (added or removed) with the latest tag array
placeholderstring"新增標籤⋯"Placeholder text of the input
maxTagsnumberMaximum number of tags; adding stops once it is reached
disabledbooleanfalseDisables the whole input
classNamestringAppended to the outermost container's class

How it works

  • Adding: pressing Enter or comma in the input adds one chip, with leading and trailing whitespace trimmed first; empty strings and values exactly equal to an existing tag (duplicates) are skipped, and the input is cleared either way
  • Deleting: every chip carries a remove ×; pressing Backspace while the input is empty deletes the last tag
  • Spring entrance: a new tag springs in from below on scale + y
  • FLIP close-ranks: on removal, that chip shrinks out (AnimatePresence mode="popLayout" takes it out of flow first) while the remaining chips and the input glide into place with a layout FLIP; with many tags, flex-wrap handles the line breaks
  • IME safe: pressing Enter or comma mid-composition in Chinese and other input methods does not accidentally add (it checks both isComposing and keyCode !== 229, covering Safari's habit of firing keydown only after compositionend)
  • Limit: once maxTags is set and reached, adding stops (existing tags can still be removed)

Accessibility

  • When the user has "reduce motion" enabled at the system level, the spring entrance and FLIP are fully disabled and state changes are instant (the DOM structure matches SSR)
  • The outermost container carries role="group" and an aria-label; clicking empty space directs focus to the input
  • Each chip's remove button is a native <button> with an aria-label naming the tag it removes, takes Tab focus in order, and shows a ring on focus-visible
  • The input carries an aria-label, and when disabled the disabled state applies to the remove buttons as well

On this page