mwui
GitHub

Tags Input

A free-form chip input with paste splitting, validation, and duplicate handling.

Labels

  • design
  • urgent

["design","urgent"]

Recipients

    Try pasting a@b.com, c@d.com.

    Installation

    Usage

    For values the user types rather than picks: labels, keywords, recipients, allowed domains. Where multi-select chooses from a known list, this one accepts anything that passes your validate.

    import { TagsInput } from "@/components/ui/tags-input";
    
    export function LabelField() {
      const [labels, setLabels] = React.useState<string[]>(["bug"]);
    
      return <TagsInput value={labels} onValueChange={setLabels} placeholder="Add a label…" />;
    }

    The value is a string[], so it goes straight into a form body or a database column.

    Committing a tag

    Enter commits. So does any character in delimiters, which defaults to comma and space — set it to [","] alone when tags may contain spaces:

    <TagsInput delimiters={[","]} placeholder="Add a phrase…" />

    Backspace on an empty input removes the last tag. Blur commits the draft by default; pass blurBehavior="clear" to discard it, or "keep" to leave it in place.

    Pasting

    Pasting text that contains delimiters, newlines, or tabs adds every part at once, which is what makes this usable for a column copied out of a spreadsheet:

    alice@example.com, bob@example.com, carol@example.com

    A paste with no delimiter is left alone as ordinary text, so a single value can still be edited before committing.

    Validation

    validate returns true to accept, or a message to reject. The message renders under the field and is wired up with aria-describedby.

    <TagsInput
      validate={(tag) => (tag.includes("@") ? true : `${tag} is not an email address.`)}
      onValueChange={setRecipients}
    />

    Duplicates are rejected silently and the existing chip flashes instead — repeating "already added" as an error is noise for something the user can see.

    Props

    PropDescription
    value / defaultValue / onValueChangeControlled or uncontrolled string[].
    delimitersCharacters that commit the draft. Defaults to [",", " "].
    maxMaximum number of tags. The input disables at the limit.
    allowDuplicatesPermit repeated values. Off by default.
    validate(tag) => true | string. A string is shown as the error message.
    blurBehavior"add" (default), "clear", or "keep".
    nameEmits one hidden input per tag, so the field posts in a plain HTML form.
    aria-labelLabels the chip list. Defaults to "Tags".

    Accessibility

    Tags are a list of li elements, each with its own labelled remove button, so a screen reader announces the count and reads every tag. Rejections use role="alert". Clicking anywhere in the field forwards focus to the input.