MultiValueInput
freeMultiValueInput turns repeated text entries into removable tag values.
@floating-ui/react
Preview
Dark
PriorityRetailFollow-up
import MultiValueInput from '@/components/ui/MultiValueInput';
export default function UsageDemo() {
return (
<div className="mx-auto w-full max-w-md">
<MultiValueInput
aria-label="Customer labels"
placeholder="Add labels"
defaultValue={['Priority', 'Retail', 'Follow-up']}
/>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add MultiValueInputExamples
Basic
Preview
Dark
ReactTypeScript
import MultiValueInput from '@/components/ui/MultiValueInput';
export default function BasicDemo() {
return (
<div className="mx-auto w-full max-w-md">
<MultiValueInput
aria-label="Technology tags"
placeholder="Type and press Enter"
defaultValue={['React', 'TypeScript']}
/>
</div>
);
}Controlled
Preview
Dark
JavaScriptCSS
Current skills: JavaScript, CSS
import { useState } from 'react';
import MultiValueInput from '@/components/ui/MultiValueInput';
export default function ControlledDemo() {
const [skills, setSkills] = useState(['JavaScript', 'CSS']);
return (
<div className="mx-auto w-full max-w-md space-y-2">
<MultiValueInput
aria-label="Skills"
value={skills}
onChange={setSkills}
placeholder="Add skills"
/>
<div className="text-xs text-muted-foreground">
Current skills: {skills.join(', ') || 'None'}
</div>
</div>
);
}Validation
Preview
Dark
import { useState } from 'react';
import Form from '@/components/ui/Form';
import MultiValueInput from '@/components/ui/MultiValueInput';
function isEmail(value: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
export default function ValidationDemo() {
const [error, setError] = useState('');
const validateEmail = (tag: string) => {
const valid = isEmail(tag);
setError(valid ? '' : 'Enter a valid email address.');
return valid;
};
return (
<Form className="mx-auto w-full max-w-md">
<Form.Field
label="Recipients"
invalid={Boolean(error)}
errorMessage={error}
>
<MultiValueInput
aria-label="Recipients"
placeholder="name@example.com"
validate={validateEmail}
onTagAdd={() => setError('')}
defaultValue={['team@example.com']}
/>
</Form.Field>
</Form>
);
}Max Tags
Preview
Dark
DesignFrontend
2 of 4 tags used.
import { useState } from 'react';
import MultiValueInput from '@/components/ui/MultiValueInput';
export default function MaxTagsDemo() {
const [tags, setTags] = useState(['Design', 'Frontend']);
const limit = 4;
return (
<div className="mx-auto w-full max-w-md space-y-2">
<MultiValueInput
aria-label="Tags"
value={tags}
onChange={setTags}
maxTags={limit}
placeholder="Add up to four tags"
/>
<div className="text-xs text-muted-foreground">
{tags.length} of {limit} tags used.
</div>
</div>
);
}Size
Preview
Dark
Small
Medium
Large
import MultiValueInput from '@/components/ui/MultiValueInput';
export default function SizeDemo() {
return (
<div className="mx-auto w-full max-w-md space-y-4">
<MultiValueInput
aria-label="Small tags"
size="sm"
placeholder="Small input"
defaultValue={['Small']}
/>
<MultiValueInput
aria-label="Medium tags"
placeholder="Medium input"
defaultValue={['Medium']}
/>
<MultiValueInput
aria-label="Large tags"
size="lg"
placeholder="Large input"
defaultValue={['Large']}
/>
</div>
);
}Overflow
Preview
Dark
NorthwindWholesalePriorityQ3RenewalPartner
import MultiValueInput from '@/components/ui/MultiValueInput';
const tags = [
'Northwind',
'Wholesale',
'Priority',
'Q3',
'Renewal',
'Partner',
];
export default function OverflowDemo() {
return (
<div className="mx-auto w-full max-w-[280px]">
<MultiValueInput
aria-label="Customer segments"
placeholder="Add segment"
defaultValue={tags}
/>
</div>
);
}Disabled
Preview
Dark
ArchivedLocked
import MultiValueInput from '@/components/ui/MultiValueInput';
export default function DisabledDemo() {
return (
<div className="mx-auto w-full max-w-md">
<MultiValueInput
aria-label="Archived tags"
disabled
placeholder="Disabled input"
defaultValue={['Archived', 'Locked']}
/>
</div>
);
}API
MultiValueInput renders a tag list plus an inline text input. Press Enter to add a value, use Backspace/Delete to remove focused tags, and click the overflow counter to inspect hidden tags.
| Prop | Description | Type | Default |
|---|---|---|---|
className | Class names applied to the root control. | string | '' |
defaultValue | Initial tags for uncontrolled usage. | string[] | [] |
disabled | Prevents editing and tag removal, and applies disabled control styling. | boolean | false |
invalid | Applies invalid styling; also inherits invalid state from Form.Field. | boolean | - |
maxTags | Maximum number of tags that can be added. | number | - |
onChange | Called with the next tag list whenever tags change. | MultiValueChangeHandler | - |
onTagAdd | Called after a tag is accepted. | MultiValueTagHandler | - |
onTagRemove | Called after a tag is removed. | MultiValueTagHandler | - |
placeholder | Placeholder text shown while no tags are present. | string | - |
readOnly | Marks the text input read-only. Removal buttons still follow disabled. | boolean | - |
size | Control size. Falls back to InputGroup size, then Form size, then ConfigProvider control size. | ControlSize | ConfigProvider controlSize |
validate | Returns whether a new tag should be accepted. | MultiValueValidator | - |
value | Controlled tag list. | string[] | - |
...nativeDivProps | Native props applied to the root div, except onChange and defaultValue. | NativeMultiValueInputProps | - |
Types
type ControlSize = 'sm' | 'md' | 'lg';
type MultiValueChangeHandler = (tags: string[]) => void;
type MultiValueTagHandler = (tag: string, allTags: string[]) => void;
type MultiValueValidator = (tag: string) => boolean;
type NativeMultiValueInputProps = Omit<
HTMLAttributes<HTMLDivElement>,
'onChange' | 'defaultValue'
>;