Components
TagInput
A list of short values typed into one row: keywords, symbols, addresses. Enter or comma commits, Backspace removes the last chip, and a pasted list arrives as separate tags.
Examples
Values with a shape
`validate` returns a message and the candidate never enters the list — validation belongs here, not in a toast.
Uppercase tickers, optionally venue-qualified.
Up to five addresses; duplicates are refused.
2 tags · 0 recipients
<Field label="Watchlist" description="Uppercase tickers, optionally venue-qualified.">
<TagInput
value={symbols}
onValueChange={setSymbols}
placeholder="Add a symbol…"
validate={(tag) => (SYMBOL_PATTERN.test(tag) ? undefined : "Use a ticker like BTC-USD")}
/>
</Field>
<Field label="Report recipients" description="Up to five addresses; duplicates are refused.">
<TagInput
value={emails}
onValueChange={setEmails}
maxTags={5}
placeholder="name@acme.dev"
validate={(tag) => (tag.includes("@") ? undefined : "That is not an email address")}
/>
</Field>States
Empty, filled, read-only and disabled are one input with four treatments — the chips do not change shape, only their editability does.
Empty
Filled
Read-only
Disabled
<TagInput aria-label="Empty tags" placeholder="Add a value…" />
<TagInput aria-label="Filled tags" defaultValue={["momentum", "carry", "value"]} />
<TagInput aria-label="Read-only tags" readOnly defaultValue={["BTC-USD"]} />
<TagInput aria-label="Disabled tags" disabled defaultValue={["BTC-USD"]} />The same chips, read-only
The chips a tag input produces are the `Tag` component used everywhere else in the library, so a read-only list and an editable one look identical.
{symbols.map((symbol) => (
<Tag key={symbol}>{symbol}</Tag>
))}Usage
import { TagInput } from "@luc/ui";<TagInput value={symbols} onValueChange={setSymbols} placeholder="Add a symbol…" />`value` plus `onValueChange` controls the list; `defaultValue` leaves it to the component. A `Field` supplies the label and the message a refused value is announced through.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
TagInput | 16 documented props | ({ value, defaultValue, onValueChange, placeholder, maxTags, separators, validate, caseSensitive, allowDuplicates, size, invalid, disabled, readOnly, id, className, ...props }: TagInputProps) => impo… |
TagInput props
Type: TagInputProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
allowDuplicates? | boolean | undefined | — | |
aria-label? | string | undefined | — | |
caseSensitive? | boolean | undefined | — | Trims and lowercases before comparing for duplicates. Default `false`. |
className? | string | undefined | — | |
defaultValue? | string[] | undefined | — | |
disabled? | boolean | undefined | — | |
id? | string | undefined | — | |
invalid? | boolean | undefined | — | |
maxTags? | number | undefined | — | Maximum tags. Further input is refused with an inline message. |
onValueChange? | ((tags: string[]) => void) | undefined | — | |
placeholder? | string | undefined | — | |
readOnly? | boolean | undefined | — | |
separators? | string[] | undefined | — | Keys that commit the current text. Enter always commits. |
size? | ControlSize | undefined | — | |
validate? | ((tag: string, tags: string[]) => string | undefined) | undefined | — | Return a message to reject the candidate value. |
value? | string[] | undefined | — |
Guidelines
Do
- Give `validate` both the rule and the sentence a reader sees when it fails: a rejected tag never enters the list.
- Set `maxTags` when the list has a real ceiling — recipients, venues, sectors — instead of letting it grow without limit.
- Use `readOnly` for a list a reader may copy but not edit; the chips keep their shape either way.
Don't
- Report a rejected value in a toast: the message belongs beside the field that refused it.
- Use a tag input for long text — the chips are for short identifiers a reader recognises at a glance.
- Expect a pasted list to arrive as one tag: a pasted delimiter-separated list is split into separate values.
Accessibility
The chips are the same `Tag` component used elsewhere in the library, and the input keeps its own label, description and message through `Field`. Enter or comma commits a value, Backspace removes the last chip, and a value the `validate` function refuses is announced through the field's message rather than silently dropped.
Related