Tag input
ProA multi-value text input that turns entries into removable chips, with keyboard support, a suggestions list, a maximum count and validation.
npx prodular-ui add tag-inputPart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
Email invites
Each address is validated before it becomes a chip. Pasting a comma separated list adds them all.
Press Enter or comma after each address. Paste a list to add several.
2/8
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"import { TagInput } from "@/components/ui/tag-input"
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/
export default function TagInputInvite() { const [emails, setEmails] = React.useState(["maya@northwind.io", "theo@northwind.io"]) return ( <div className="flex w-full max-w-md flex-col gap-2"> <label htmlFor="invite-emails" className="text-sm font-medium"> Invite teammates </label> <p id="invite-hint" className="-mt-1 text-[13px] text-muted-foreground"> Press Enter or comma after each address. Paste a list to add several. </p> <TagInput id="invite-emails" aria-describedby="invite-hint" value={emails} onValueChange={setEmails} max={8} transform={(t) => t.trim().toLowerCase()} validate={(t) => (emailPattern.test(t) ? null : `${t} is not a valid email address.`)} placeholder="name@company.com" /> <div className="flex justify-end"> <Button disabled={!emails.length}> Send {emails.length} {emails.length === 1 ? "invite" : "invites"} </Button> </div> </div> )}Labels with suggestions
Arrow keys move through suggestions, Enter or Tab picks one. Only known labels are accepted, up to five.
2/5
"use client"
import { TagInput } from "@/components/ui/tag-input"
const labels = [ "billing", "bug", "churn-risk", "enterprise", "feature-request", "integrations", "onboarding", "priority", "renewal", "sso", "webhooks",]
export default function TagInputSuggestions() { return ( <div className="flex w-full max-w-md flex-col gap-2 self-start"> <label htmlFor="ticket-labels" className="text-sm font-medium"> Labels </label> <TagInput id="ticket-labels" defaultValue={["billing", "priority"]} suggestions={labels} restrictToSuggestions max={5} transform={(t) => t.trim().toLowerCase().replace(/\s+/g, "-")} placeholder="Type to search labels" /> </div> )}Anatomy
<label htmlFor="emails">Invite teammates</label><TagInput id="emails" value={emails} onValueChange={setEmails} max={8} validate={(t) => (isEmail(t) ? null : `${t} is not a valid email address.`)}/>Guidance
Use it when
- Free-form lists of short values: email recipients, labels, allowed domains, keywords.
- suggestions when there is a known vocabulary, with restrictToSuggestions when only those values are valid.
Avoid
- Picking from a short fixed list. Use a multi-select combobox or checkboxes.
- Long values like sentences. Chips truncate.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Always connect a visible label with id, and describe the delimiters in helper text linked with aria-describedby.
- Validate with validate and return a plain-language message. Rejected entries stay in the input so people can fix them.
- Normalize with transform (trim, lowercase) instead of post-processing onValueChange.
- Set max when the backend has a limit; the input shows a counter and disables itself at the limit.
- Pass name to submit with a native form. One hidden input is rendered per tag.
API
TagInput
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue / onValueChange | string[] | Controlled or uncontrolled list of tags. | |
| suggestions | string[] | Shown in a listbox that filters as people type. | |
| restrictToSuggestions | boolean | false | Reject values that are not in suggestions. |
| max | number | Maximum number of tags. Shows a counter. | |
| validate | (tag, tags) => string | null | Return a message to reject the tag. | |
| transform | (tag) => string | trim | Normalizes each tag before validation. |
| delimiters | string[] | ["Enter", ","] | Keys that commit the typed text. |
| allowDuplicates | boolean | false | Duplicates are compared case-insensitively. |
| name | string | Renders hidden inputs for form posts. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add tag-input# or, with the registry in components.jsonnpx shadcn@latest add @prodular/tag-input