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

# TagMultiselect

> [Source](https://github.com/BearBin1215/ooui-react/tree/main/src/widgets/TagMultiselect) | [Original component](https://doc.wikimedia.org/oojs-ui/master/js/OO.ui.TagMultiselectWidget.html "OO.ui.TagMultiselectWidget")

A tag input: values are shown as removable chips. Free-entry form — for "type to filter + dropdown candidates" use [MenuTagMultiselect](/ooui-react/en/components/menu-tag-multiselect/index.md).

## 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.

```tsx preview
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.

```tsx preview
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

| Prop                           | Description                                                           | Type                                            | Default    |
| ------------------------------ | --------------------------------------------------------------------- | ----------------------------------------------- | ---------- |
| `value`                        | Tag value set (controlled; passing it enables control)                | `(string \| number)[]`                          | —          |
| `defaultValue`                 | Initial value for uncontrolled use                                    | `(string \| number)[]`                          | —          |
| `onChange`                     | Add/remove callback (value-first, returns the scalar array)           | `ChangeHandler<(string \| number)[]>`           | —          |
| `allowedValues`                | Legal-value whitelist (the constraint without a menu)                 | `(string \| number)[]`                          | —          |
| `allowArbitrary`               | Allow arbitrary values (skips the range test)                         | `boolean`                                       | `false`    |
| `allowDuplicates`              | Allow repeated values                                                 | `boolean`                                       | `false`    |
| `allowReordering`              | Allow drag reordering                                                 | `boolean`                                       | `true`     |
| `allowEditTags`                | Allow clicking a tag to edit it in place                              | `boolean`                                       | `true`     |
| `allowDisplayInvalidTags`      | Keep invalid values shown as tags (flagged invalid)                   | `boolean`                                       | `false`    |
| `tagLimit`                     | Maximum tag count                                                     | `number`                                        | —          |
| `inputPosition`                | Input position                                                        | `'inline' \| 'outline' \| 'none'`               | `'inline'` |
| `onInvalidTagsChange`          | Invalid-tag set change callback (read-only, doesn't change the value) | `(invalidValues: (string \| number)[]) => void` | —          |
| `placeholder` / `name`         | Input placeholder / `name`                                            | `string`                                        | —          |
| `icon` / `indicator` / `flags` | Icon / indicator / flags                                              | —                                               | —          |
| `disabled`                     | Whether disabled                                                      | `boolean`                                       | `false`    |
| `...rest`                      | Native `div` props passed straight to the root                        | `HTMLAttributes<HTMLDivElement>`                | —          |

:::note 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](/ooui-react/en/components/menu-tag-multiselect/index.md)
 options and look it up by `value`
 in your own `options`
. 
:::
## See also

- Tag input with a candidate menu: [MenuTagMultiselect](/ooui-react/en/components/menu-tag-multiselect/index.md)
- The `label` / `labelText` / `data` option contract: [Selection and options](/ooui-react/en/guide/options.md#options-of-tag-inputs)
