Multi Input
A field that collects several values as removable chips in one row.
Overview
MultiInput is one field that holds a list. Each committed entry becomes a chip with its own remove button, and the text input stays on the same row so the field grows downward as the list fills.
An entry is committed by Enter, by typing or pasting one of the separators, or by leaving the field. Entries are trimmed, blanks are dropped, and repeats are ignored unless allowDuplicates is set. Pass name to submit the list through a native form, or value and onValueChange to drive it from state.
Pass options to make it a multi-select: the field opens a dropdown of the remaining choices, narrows it as you type, and commits the highlighted one on Enter or a click. Add allowCustomValues={false} when only listed values may be entered — typed or pasted text then has to match an option's value or label, and anything else is rejected the way a failed validate is.
Preview
Customize
Installation
Add the component with the CLI. It copies the files in and installs anything they depend on.
npx vinyaas add multi-inputUsage
import { MultiInput } from "@/components/ui/multi-input";
export function Recipients() {
return (
<MultiInput
aria-label="Recipients"
placeholder="Add a recipient…"
defaultValue={["ada@vinyaas.dev"]}
/>
);
}Examples
Default
Type an entry and press Enter, or end it with a comma. Backspace in an empty field removes the last value.
With a label
id points the Label at the inner text input.
Separators
Any listed character ends an entry, so a pasted list splits into values in one step.
Validation
A rejected entry stays in the field and marks it invalid, so the text can be corrected instead of lost.
Dropdown
options turns the field into a multi-select. The list opens on focus, narrows as you type, and hides values already chosen. An option can carry its own label and be disabled.
Listed values only
allowCustomValues={false} rejects anything the list does not hold, so the field accepts dropdown values and nothing else. Typed text still matches an option by its value or its label.
Limit
max caps the list. Entries past the limit stay in the field until a value is removed.
Invalid and disabled
Both states match Input, so the field sits in a form unchanged.
In practice
An invite form collects addresses with a comma or a space, validates each one as it is committed, and caps the list at ten.
API
Other attributes for the underlying element are passed through.
| Name | Type | Default | Description |
|---|---|---|---|
value | string[] | — | Controlled values. Pair it with onValueChange. |
defaultValue | string[] | [] | Starting values when the field is uncontrolled. |
onValueChange | (values: string[]) => void | — | Fires with the next list whenever a value is added or removed. |
separators | string[] | [","] | Characters that end an entry while typing or pasting. Enter and blur always commit. |
placeholder | string | — | Shown while the field holds no values. |
max | number | — | Maximum number of values. Entries past the limit stay in the field. |
validate | (value: string) => boolean | — | Return false to keep an entry in the field instead of committing it. |
allowDuplicates | boolean | false | Keeps repeated entries instead of dropping them. |
options | (string | MultiInputOption)[] | — | Turns the field into a multi-select. Typing filters the list and the field offers it in a dropdown. |
allowCustomValues | boolean | true | With options, set false to accept listed values only. Text matching no option is rejected. |
emptyMessage | string | "No matches." | Shown when the dropdown has nothing left to offer. |
disabled | boolean | false | Disables the text input and every remove button. |
invalid | boolean | false | Marks the field invalid. A rejected entry also sets this on its own. |
name | string | — | Submits one hidden input per value so the field works in a native form. |
id | string | — | Applied to the inner text input so a Label can point at it. |
Accessibility
- Name the field with
aria-label, or give it anidand point a Label at it. - Each remove button is a native button in the tab order, named
Remove {value}, so the list can be emptied without a pointer. - Backspace in an empty field removes the last value — a shortcut, not the only way to remove one.
- With
optionsthe text input is acomboboxthat owns the listbox: Arrow Down and Arrow Up move the highlight through the choices, Enter commits the highlighted one, and Escape closes the list without touching a surrounding dialog. - A rejected entry sets
aria-invalidon the text input. Pair it with your own message when the reason is not obvious.