MultiValueInput

free

MultiValueInput 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 MultiValueInput

Examples

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
team@example.com
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.

PropDescriptionTypeDefault
classNameClass names applied to the root control.string''
defaultValueInitial tags for uncontrolled usage.string[][]
disabledPrevents editing and tag removal, and applies disabled control styling.booleanfalse
invalidApplies invalid styling; also inherits invalid state from Form.Field.boolean-
maxTagsMaximum number of tags that can be added.number-
onChangeCalled with the next tag list whenever tags change.MultiValueChangeHandler-
onTagAddCalled after a tag is accepted.MultiValueTagHandler-
onTagRemoveCalled after a tag is removed.MultiValueTagHandler-
placeholderPlaceholder text shown while no tags are present.string-
readOnlyMarks the text input read-only. Removal buttons still follow disabled.boolean-
sizeControl size. Falls back to InputGroup size, then Form size, then ConfigProvider control size.ControlSizeConfigProvider controlSize
validateReturns whether a new tag should be accepted.MultiValueValidator-
valueControlled tag list.string[]-
...nativeDivPropsNative 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'
>;