{"slug":"tags-input","title":"Tags Input","description":"Using the tags input machine in your project.","contentType":"component","framework":"react","content":"Tags input renders tags inside a control, followed by an actual text input. By\ndefault, tags are added when text is typed in the input field and the `Enter` or\n`Comma` key is pressed. Throughout the interaction, DOM focus remains on the\ninput element.\n\n## Resources\n\n\n[Latest version: v1.39.1](https://www.npmjs.com/package/@zag-js/tags-input)\n[Logic Visualizer](https://zag-visualizer.vercel.app/tags-input)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/tags-input)\n\n\n\n**Features**\n\n- Typing in the input and pressing Enter adds new tags\n- Clear trigger to remove all tags\n- Add tags by pasting into the input\n- Delete tags on backspace\n- Edit tags after creation\n- Limit the number of tags\n- Navigate tags with keyboard\n- Custom validation to accept/reject tags\n\n## Installation\n\nInstall the tags input package:\n\n```bash\nnpm install @zag-js/tags-input @zag-js/react\n# or\nyarn add @zag-js/tags-input @zag-js/react\n```\n\n## Anatomy\n\nTo set up the tags input correctly, you'll need to understand its anatomy and\nhow we name its parts.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nImport the tags input package:\n\n```tsx\nimport * as tagsInput from \"@zag-js/tags-input\"\n```\n\nThe tags input package exports two key functions:\n\n- `machine` - State machine logic.\n- `connect` - Maps machine state to JSX props and event handlers.\n\nThen use the framework integration helpers:\n\n```tsx\nimport * as tagsInput from \"@zag-js/tags-input\"\nimport { useMachine, normalizeProps } from \"@zag-js/react\"\nimport { useId } from \"react\"\n\nexport function TagsInput() {\n  const service = useMachine(tagsInput.machine, {\n    id: useId(),\n    value: [\"React\", \"Vue\"],\n  })\n\n  const api = tagsInput.connect(service, normalizeProps)\n\n  return (\n    <div {...api.getRootProps()}>\n      {api.value.map((value, index) => (\n        <span key={index} {...api.getItemProps({ index, value })}>\n          <div {...api.getItemPreviewProps({ index, value })}>\n            <span>{value} </span>\n            <button {...api.getItemDeleteTriggerProps({ index, value })}>\n              &#x2715;\n            </button>\n          </div>\n          <input {...api.getItemInputProps({ index, value })} />\n        </span>\n      ))}\n      <input placeholder=\"Add tag...\" {...api.getInputProps()} />\n    </div>\n  )\n}\n```\n\n### Navigating and editing tags\n\nWhen the input has an empty value or the caret is at the start position, the\ntags can be selected with the left and right arrow keys. When a tag has visual\nfocus:\n\n- Press `Enter` or double-click the tag to edit it, then press `Enter` to commit\n  the change.\n- Pressing `Delete` or `Backspace` will delete the tag that has \"visual\" focus.\n\n### Setting the initial tags\n\nTo set the initial tag values, pass the `defaultValue` property in the machine's\ncontext.\n\n```tsx {2}\nconst service = useMachine(tagsInput.machine, {\n  defaultValue: [\"React\", \"Redux\", \"TypeScript\"],\n})\n```\n\n### Controlled tags input\n\nTo control the tags input programmatically, pass the `value` and `onValueChange`\nproperties to the machine function.\n\n```tsx\nimport { useState } from \"react\"\n\nexport function ControlledTagsInput() {\n  const [value, setValue] = useState([\"React\", \"Vue\"])\n\n  const service = useMachine(tagsInput.machine, {\n    value,\n    onValueChange(details) {\n      setValue(details.value)\n    },\n  })\n\n  return ( // ... )\n}\n```\n\n### Controlled input value\n\nUse `inputValue` and `onInputValueChange` when you need to control the typing\nstate separately.\n\n```tsx\nconst service = useMachine(tagsInput.machine, {\n  inputValue,\n  onInputValueChange(details) {\n    setInputValue(details.inputValue)\n  },\n})\n```\n\n### Removing all tags\n\nThe tags input will remove all tags when the clear button is clicked. To remove\nall tags, use the provided `clearTriggerProps` function from the `api`.\n\n```tsx {4}\n//...\n<div {...api.getControlProps()}>\n  <input {...api.getInputProps()} />\n  <button {...api.getClearTriggerProps()} />\n</div>\n//...\n```\n\nTo programmatically remove all tags, use `api.clearValue()`.\n\n### Usage within forms\n\nThe tags input works when placed within a form and the form is submitted. We\nachieve this by:\n\n- ensuring we emit the input event as the value changes.\n- adding a `name` and `value` attribute to a hidden input so the tags can be\n  accessed in the `FormData`.\n\nTo get this feature working you need to pass a `name` option to the context and\nrender the hidden input with `api.getHiddenInputProps()`.\n\n```tsx {2}\nconst service = useMachine(tagsInput.machine, {\n  name: \"tags\",\n  defaultValue: [\"React\", \"Redux\", \"TypeScript\"],\n})\n```\n\n### Limiting the number of tags\n\nTo limit the number of tags within the component, you can set the `max` property\nto the limit you want. The default value is `Infinity`.\n\nWhen the tag reaches the limit, new tags cannot be added except the\n`allowOverflow` option is passed to the context.\n\n```tsx {2-3}\nconst service = useMachine(tagsInput.machine, {\n  max: 10,\n  allowOverflow: true,\n})\n```\n\n### Validating Tags\n\nBefore a tag is added, the machine provides a `validate` function you can use to\ndetermine whether to accept or reject a tag.\n\n> **Note:** Duplicate tags are prevented by default. Set `allowDuplicates: true`\n> to allow repeatable tags (e.g. sentence builders). Use `validate` for custom\n> rules like format or length.\n\nCommon use-cases for validating tags include enforcing format, length, or\ncontent rules.\n\n```tsx {2-4}\nconst service = useMachine(tagsInput.machine, {\n  validate(details) {\n    // Example: only allow lowercase alphabetic tags\n    return /^[a-z]+$/.test(details.inputValue)\n  },\n})\n```\n\n### Allow Duplicates\n\nBy default, duplicate tags are prevented. For use cases like sentence builders\nor repeatable tokens, set `allowDuplicates: true`:\n\n```tsx {2}\nconst service = useMachine(tagsInput.machine, {\n  allowDuplicates: true,\n})\n```\n\n### Blur behavior\n\nWhen the tags input is blurred, you can configure the action the machine should\ntake by passing the `blurBehavior` option to the context.\n\n- `\"add\"` — Adds the tag to the list of tags.\n- `\"clear\"` — Clears the tags input value.\n\n```tsx {2}\nconst service = useMachine(tagsInput.machine, {\n  blurBehavior: \"add\",\n})\n```\n\n### Paste behavior\n\nTo add a tag when a arbitrary value is pasted in the input element, pass the\n`addOnPaste` option.\n\nWhen a value is pasted, the machine will:\n\n- check if the value is a valid tag based on the `validate` option\n- split the value by the `delimiter` option passed\n\n```tsx {2}\nconst service = useMachine(tagsInput.machine, {\n  addOnPaste: true,\n})\n```\n\n### Disable tag editing\n\nBy default, tags can be edited by double-clicking a tag or focusing it and\npressing `Enter`. To disable this behavior, set `editable: false`.\n\n```tsx {2}\nconst service = useMachine(tagsInput.machine, {\n  editable: false,\n})\n```\n\n### Custom tag delimiter\n\nUse `delimiter` to control how tags are created from key presses and paste.\n\n```tsx\nconst service = useMachine(tagsInput.machine, {\n  delimiter: \";\",\n})\n```\n\n### Listening for events\n\nDuring the lifetime of the tags input interaction, here's a list of events we\nemit:\n\n- `onValueChange` — invoked when the tag value changes.\n- `onHighlightChange` — invoked when a tag has visual focus.\n- `onValueInvalid` — invoked when the max tag count is reached or the `validate`\n  function returns `false`.\n\n```tsx\nconst service = useMachine(tagsInput.machine, {\n  onValueChange(details) {\n    // details => { value: string[] }\n    console.log(\"tags changed to:\", details.value)\n  },\n  onHighlightChange(details) {\n    // details => { highlightedValue: string | null }\n    console.log(\"highlighted tag:\", details.highlightedValue)\n  },\n  onValueInvalid(details) {\n    console.log(\"Invalid!\", details.reason)\n  },\n})\n```\n\n### Programmatic tag operations\n\nUse the API for imperative updates.\n\n```tsx\napi.addValue(\"React\")\napi.setValue([\"React\", \"TypeScript\"])\napi.clearValue() // clear all\napi.clearValue(\"React\") // clear one\napi.setValueAtIndex(0, \"Vue\")\napi.focus()\n```\n\n## Styling guide\n\nEach part includes a `data-part` attribute you can target in CSS.\n\n### Focused state\n\nThe input is focused when the user clicks on it. In this focused state, the root\nand label receive `data-focus`.\n\n```css\n[data-part=\"root\"][data-focus] {\n  /* styles for root focus state */\n}\n\n[data-part=\"label\"][data-focus] {\n  /* styles for label focus state */\n}\n\n[data-part=\"input\"]:focus {\n  /* styles for input focus state */\n}\n```\n\n### Invalid state\n\nWhen the tags input is invalid by setting the `invalid: true` in the machine's\ncontext, the `data-invalid` attribute is set on the root, input, control, and\nlabel.\n\n```css\n[data-part=\"root\"][data-invalid] {\n  /* styles for invalid state for root */\n}\n\n[data-part=\"label\"][data-invalid] {\n  /*  styles for invalid state for label */\n}\n\n[data-part=\"input\"][data-invalid] {\n  /*  styles for invalid state for input */\n}\n```\n\n### Disabled state\n\nWhen the tags input is disabled by setting the `disabled: true` in the machine's\ncontext, the `data-disabled` attribute is set on the root, input, control and\nlabel.\n\n```css\n[data-part=\"root\"][data-disabled] {\n  /* styles for disabled state for root */\n}\n\n[data-part=\"label\"][data-disabled] {\n  /* styles for disabled state for label */\n}\n\n[data-part=\"input\"][data-disabled] {\n  /* styles for disabled state for input */\n}\n\n[data-part=\"control\"][data-disabled] {\n  /* styles for disabled state for control */\n}\n```\n\nWhen a tag is disabled, the `data-disabled` attribute is set on the tag.\n\n```css\n[data-part=\"item-preview\"][data-disabled] {\n  /* styles for disabled tag  */\n}\n```\n\n### Highlighted state\n\nWhen a tag is highlighted via the keyboard navigation or pointer hover, a\n`data-highlighted` attribute is set on the tag.\n\n```css\n[data-part=\"item-preview\"][data-highlighted] {\n  /* styles for visual focus */\n}\n```\n\n### Readonly state\n\nWhen the tags input is in its readonly state, the `data-readonly` attribute is\nset on the root, label, input and control.\n\n```css\n[data-part=\"root\"][data-readonly] {\n  /* styles for readonly for root */\n}\n\n[data-part=\"control\"][data-readonly] {\n  /* styles for readonly for control */\n}\n\n[data-part=\"input\"][data-readonly] {\n  /* styles for readonly for input  */\n}\n\n[data-part=\"label\"][data-readonly] {\n  /* styles for readonly for label */\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe tags input machine exposes the following context properties:\n\n**`ids`**\nType: `Partial<{ root: string; input: string; hiddenInput: string; clearBtn: string; label: string; control: string; item: (opts: ItemProps) => string; itemDeleteTrigger: (opts: ItemProps) => string; itemInput: (opts: ItemProps) => string; }>`\nDescription: The ids of the elements in the tags input. Useful for composition.\n\n**`translations`**\nType: `IntlTranslations`\nDescription: Specifies the localized strings that identifies the accessibility elements and their states\n\n**`maxLength`**\nType: `number`\nDescription: The max length of the input.\n\n**`delimiter`**\nType: `string | RegExp`\nDescription: The character that serves has:\n- event key to trigger the addition of a new tag\n- character used to split tags when pasting into the input\n\n**`autoFocus`**\nType: `boolean`\nDescription: Whether the input should be auto-focused\n\n**`disabled`**\nType: `boolean`\nDescription: Whether the tags input should be disabled\n\n**`readOnly`**\nType: `boolean`\nDescription: Whether the tags input should be read-only\n\n**`invalid`**\nType: `boolean`\nDescription: Whether the tags input is invalid\n\n**`required`**\nType: `boolean`\nDescription: Whether the tags input is required\n\n**`editable`**\nType: `boolean`\nDescription: Whether a tag can be edited after creation, by pressing `Enter` or double clicking.\n\n**`inputValue`**\nType: `string`\nDescription: The controlled tag input's value\n\n**`defaultInputValue`**\nType: `string`\nDescription: The initial tag input value when rendered.\nUse when you don't need to control the tag input value.\n\n**`value`**\nType: `string[]`\nDescription: The controlled tag value\n\n**`defaultValue`**\nType: `string[]`\nDescription: The initial tag value when rendered.\nUse when you don't need to control the tag value.\n\n**`onValueChange`**\nType: `(details: ValueChangeDetails) => void`\nDescription: Callback fired when the tag values is updated\n\n**`onInputValueChange`**\nType: `(details: InputValueChangeDetails) => void`\nDescription: Callback fired when the input value is updated\n\n**`onHighlightChange`**\nType: `(details: HighlightChangeDetails) => void`\nDescription: Callback fired when a tag is highlighted by pointer or keyboard navigation\n\n**`onValueInvalid`**\nType: `(details: ValidityChangeDetails) => void`\nDescription: Callback fired when the max tag count is reached or the `validateTag` function returns `false`\n\n**`validate`**\nType: `(details: ValidateArgs) => boolean`\nDescription: Returns a boolean that determines whether a tag can be added.\nUseful for preventing duplicates or invalid tag values.\n\n**`allowDuplicates`**\nType: `boolean`\nDescription: Whether to allow duplicate tags.\n\n**`blurBehavior`**\nType: `\"clear\" | \"add\"`\nDescription: The behavior of the tags input when the input is blurred\n- `\"add\"`: add the input value as a new tag\n- `\"clear\"`: clear the input value\n\n**`addOnPaste`**\nType: `boolean`\nDescription: Whether to add a tag when you paste values into the tag input\n\n**`max`**\nType: `number`\nDescription: The max number of tags\n\n**`allowOverflow`**\nType: `boolean`\nDescription: Whether to allow tags to exceed max. In this case,\nwe'll attach `data-invalid` to the root\n\n**`sanitizeValue`**\nType: `(value: string) => string`\nDescription: Function to sanitize the tag value before adding.\nUseful for trimming whitespace, normalizing case, or stripping special characters.\n\n**`name`**\nType: `string`\nDescription: The name attribute for the input. Useful for form submissions\n\n**`form`**\nType: `string`\nDescription: The associate form of the underlying input element.\n\n**`placeholder`**\nType: `string`\nDescription: The placeholder text for the input\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => ShadowRoot | Node | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n**`onPointerDownOutside`**\nType: `(event: PointerDownOutsideEvent) => void`\nDescription: Function called when the pointer is pressed down outside the component\n\n**`onFocusOutside`**\nType: `(event: FocusOutsideEvent) => void`\nDescription: Function called when the focus is moved outside the component\n\n**`onInteractOutside`**\nType: `(event: InteractOutsideEvent) => void`\nDescription: Function called when an interaction happens outside the component\n\n### Machine API\n\nThe tags input `api` exposes the following methods:\n\n**`empty`**\nType: `boolean`\nDescription: Whether the tags are empty\n\n**`inputValue`**\nType: `string`\nDescription: The value of the tags entry input.\n\n**`value`**\nType: `string[]`\nDescription: The value of the tags as an array of strings.\n\n**`valueAsString`**\nType: `string`\nDescription: The value of the tags as a string.\n\n**`count`**\nType: `number`\nDescription: The number of the tags.\n\n**`atMax`**\nType: `boolean`\nDescription: Whether the tags have reached the max limit.\n\n**`setValue`**\nType: `(value: string[]) => void`\nDescription: Function to set the value of the tags.\n\n**`clearValue`**\nType: `(id?: string) => void`\nDescription: Function to clear the value of the tags.\n\n**`addValue`**\nType: `(value: string) => void`\nDescription: Function to add a tag to the tags.\n\n**`setValueAtIndex`**\nType: `(index: number, value: string) => void`\nDescription: Function to set the value of a tag at the given index.\n\n**`setInputValue`**\nType: `(value: string) => void`\nDescription: Function to set the value of the tags entry input.\n\n**`clearInputValue`**\nType: `VoidFunction`\nDescription: Function to clear the value of the tags entry input.\n\n**`focus`**\nType: `VoidFunction`\nDescription: Function to focus the tags entry input.\n\n**`getItemState`**\nType: `(props: ItemProps) => ItemState`\nDescription: Returns the state of a tag\n\n### Data Attributes\n\n**`Root`**\n\n**`data-scope`**: tags-input\n**`data-part`**: root\n**`data-invalid`**: Present when invalid\n**`data-readonly`**: Present when read-only\n**`data-disabled`**: Present when disabled\n**`data-focus`**: Present when focused\n**`data-empty`**: Present when the content is empty\n\n**`Label`**\n\n**`data-scope`**: tags-input\n**`data-part`**: label\n**`data-disabled`**: Present when disabled\n**`data-invalid`**: Present when invalid\n**`data-readonly`**: Present when read-only\n**`data-required`**: Present when required\n\n**`Control`**\n\n**`data-scope`**: tags-input\n**`data-part`**: control\n**`data-disabled`**: Present when disabled\n**`data-readonly`**: Present when read-only\n**`data-invalid`**: Present when invalid\n**`data-focus`**: Present when focused\n\n**`Input`**\n\n**`data-scope`**: tags-input\n**`data-part`**: input\n**`data-invalid`**: Present when invalid\n**`data-readonly`**: Present when read-only\n**`data-empty`**: Present when the content is empty\n\n**`Item`**\n\n**`data-scope`**: tags-input\n**`data-part`**: item\n**`data-value`**: The value of the item\n**`data-disabled`**: Present when disabled\n\n**`ItemPreview`**\n\n**`data-scope`**: tags-input\n**`data-part`**: item-preview\n**`data-value`**: The value of the item\n**`data-disabled`**: Present when disabled\n**`data-highlighted`**: Present when highlighted\n\n**`ItemText`**\n\n**`data-scope`**: tags-input\n**`data-part`**: item-text\n**`data-disabled`**: Present when disabled\n**`data-highlighted`**: Present when highlighted\n\n**`ItemDeleteTrigger`**\n\n**`data-scope`**: tags-input\n**`data-part`**: item-delete-trigger\n**`data-disabled`**: Present when disabled\n**`data-highlighted`**: Present when highlighted\n\n**`ClearTrigger`**\n\n**`data-scope`**: tags-input\n**`data-part`**: clear-trigger\n**`data-readonly`**: Present when read-only\n\n## Accessibility\n\n### Keyboard Interactions\n\n**`ArrowLeft`**\nDescription: Moves focus to the previous tag item\n\n**`ArrowRight`**\nDescription: Moves focus to the next tag item\n\n**`Backspace`**\nDescription: Deletes the tag item that has visual focus or the last tag item\n\n**`Enter`**\nDescription: <span>When a tag item has visual focus, it puts the tag in edit mode.<br />When the input has focus, it adds the value to the list of tags</span>\n\n**`Delete`**\nDescription: Deletes the tag item that has visual focus\n\n**`Control + V`**\nDescription: When `addOnPaste` is set. Adds the pasted value as a tags","package":"@zag-js/tags-input","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/tags-input.mdx"}