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.
npx shadcn@latest add https://webberui.com/r/tag-input.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<TagInput />
Installation
npx shadcn@latest add https://webberui.com/r/tag-input.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
value | string[] | — | Controlled tag array; when omitted, the component manages it internally |
onChange | (tags: string[]) => void | — | Fires when the tags change (added or removed) with the latest tag array |
placeholder | string | "新增標籤⋯" | Placeholder text of the input |
maxTags | number | — | Maximum number of tags; adding stops once it is reached |
disabled | boolean | false | Disables the whole input |
className | string | — | Appended 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
isComposingandkeyCode !== 229, covering Safari's habit of firing keydown only aftercompositionend) - Limit: once
maxTagsis 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 anaria-label; clicking empty space directs focus to the input - Each chip's remove button is a native
<button>with anaria-labelnaming the tag it removes, takes Tab focus in order, and shows a ring onfocus-visible - The input carries an
aria-label, and when disabled thedisabledstate applies to the remove buttons as well
OTP Input
A segmented verification-code input — a single invisible input sits over the visual cells, with paste support, an error shake, and a success ripple that turns the cells green.
Vanish Input
An input that dissolves its text into pixel particles on submit, in sweep, scatter, or rise styles, with rotating placeholders.