For AI agents: the complete documentation index is available at /ooui-react/en/llms.txt, the full documentation bundle is available at /ooui-react/en/llms-full.txt, and this page is available as Markdown at /ooui-react/en/components/tag-multiselect/index.md.
  • English
  • TagMultiselect

    Source | Original component

    A tag input: values are shown as removable chips. Free-entry form — for "type to filter + dropdown candidates" use MenuTagMultiselect.

    Basic usage

    Type text in the input and press Enter to commit a tag; press Backspace (when the input is empty) at the end to remove the previous tag; click a tag to move it back into the input for editing.

    Free entry requires allowArbitrary: matching the original, without it (and without allowedValues) any typed value lies outside the legal range, so Enter is rejected (no error — simply no tag). See the next section for the whitelist form.

    React
    TypeScript
    Current tags: React, TypeScript
    import { useState } from "react";
    import { TagMultiselect } from "ooui-react";
    
    function App() {
      const [tags, setTags] = useState<(string | number)[]>(["React", "TypeScript"]);
    
      return (
        <div style={{ display: "flex", flexDirection: "column", gap: 8, maxWidth: 360 }}>
          <TagMultiselect
            value={tags}
            onChange={setTags}
            allowArbitrary
            placeholder="Press Enter to add a tag"
          />
          <div>Current tags: {tags.join(", ") || "(none)"}</div>
        </div>
      );
    }
    
    export default App;

    Keyboard editing

    • Enter commits the typed text as a tag; Backspace (when the inline input is empty) removes the last tag and backfills its text into the input for editing — holding Ctrl / Cmd removes without backfilling.
    • ← / →: with the caret at either end of the input, navigation moves between tags; moving forward past the last tag hands focus back to the input (directions flip in RTL); Escape clears the uncommitted text in the input.

    Whitelist and limits

    • allowedValues is the legal value range; tags outside it are invalid (unless allowArbitrary).
    • allowDuplicates permits repeated values; tagLimit caps the tag count, disabling input once reached.
    • allowDisplayInvalidTags keeps invalid values shown as tags (flagged invalid) rather than refusing to add them.
    import { useState } from "react";
    import { TagMultiselect } from "ooui-react";
    
    function App() {
      const [tags, setTags] = useState<(string | number)[]>([]);
    
      return (
        <div style={{ maxWidth: 360 }}>
          <TagMultiselect
            value={tags}
            onChange={setTags}
            allowedValues={["red", "green", "blue"]}
            tagLimit={2}
            placeholder="Only red / green / blue, at most 2"
          />
        </div>
      );
    }
    
    export default App;

    Note: the component's overall invalid flag (red border) is broader than onInvalidTagsChange — uncommitted text left in the input on blur also triggers it. If you see the red border without an onInvalidTagsChange callback, check for leftover text in the input first.

    Sanitizing external values: pickValidTags

    The exported pure function pickValidTags(values, { options?, allowedValues?, allowDuplicates? }) returns the valid subset of the given values — dropping values outside the legal range and duplicates (when allowDuplicates is off), using the same rules as the component (pass the component's own props to get a set complementary to onInvalidTagsChange). Use it to sanitize tags before backfilling external data into value, or before submitting.

    Input position and reordering

    inputPosition is inline (default, the input follows the last tag), outline (the input is below the tag area) or none (no input, display-only, paired with a controlled value). allowReordering (on by default) enables drag-to-reorder; when off, new tags insert according to the order in allowedValues. The original allows replacing the inner input via config.input / inputWidget; here the input is built in and cannot be replaced.

    API

    PropDescriptionTypeDefault
    valueTag value set (controlled; passing it enables control)(string | number)[]—
    defaultValueInitial value for uncontrolled use(string | number)[]—
    onChangeAdd/remove callback (value-first, returns the scalar array)ChangeHandler<(string | number)[]>—
    allowedValuesLegal-value whitelist (the constraint without a menu)(string | number)[]—
    allowArbitraryAllow arbitrary values (skips the range test)booleanfalse
    allowDuplicatesAllow repeated valuesbooleanfalse
    allowReorderingAllow drag reorderingbooleantrue
    allowEditTagsAllow clicking a tag to edit it in placebooleantrue
    allowDisplayInvalidTagsKeep invalid values shown as tags (flagged invalid)booleanfalse
    tagLimitMaximum tag countnumber—
    inputPositionInput position'inline' | 'outline' | 'none''inline'
    onInvalidTagsChangeInvalid-tag set change callback (read-only, doesn't change the value)(invalidValues: (string | number)[]) => void—
    placeholder / nameInput placeholder / namestring—
    icon / indicator / flagsIcon / indicator / flags——
    disabledWhether disabledbooleanfalse
    ...restNative div props passed straight to the rootHTMLAttributes<HTMLDivElement>—
    Differences from OOUI

    The original allows tag.data to be any object; this library always keeps a tag's identity as the scalar value, so the controlled array is serializable and diffable. To carry an object with a tag, use the data field on MenuTagMultiselect options and look it up by value in your own options.

    See also