Tags Input

A component that allows users to add tags to an input field.

React
Solid
Vue

Anatomy

To set up the tags input correctly, you’ll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Examples

Learn how to use the TagsInput component in your project. Let’s take a look at the most basic example:

import { TagsInput } from '@ark-ui/react'

const Basic = () => {
  return (
    <TagsInput.Root>
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

When the input has an empty value or the caret is at the start position, the tags can be selected by using the arrow left and arrow right keys. When “visual” focus in on any tag:

  • Pressing Enter or double-clicking on the tag will put it in edit mode, allowing the user change its value and press Enter to commit the changes.
  • Pressing Delete or Backspace will delete the tag that has visual focus.

Setting the initial tags

To set the initial tag values, set the defaultValue prop.

import { TagsInput } from '@ark-ui/react'

const InitialValue = () => {
  return (
    <TagsInput.Root defaultValue={['React', 'Solid', 'Vue']}>
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add tag" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Limiting the number of tags

To limit the number of tags within the component, you can set the max property to the limit you want. The default value is Infinity.

When the tag reaches the limit, new tags cannot be added except the allowOverflow prop is set to true.

import { TagsInput } from '@ark-ui/react'

const MaxWithOverflow = () => {
  return (
    <TagsInput.Root max={3} allowOverflow>
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Validating Tags

Before a tag is added, the validate function is called to determine whether to accept or reject a tag.

A common use-case for validating tags is preventing duplicates or validating the data type.

import { TagsInput } from '@ark-ui/react'

const Validated = () => {
  return (
    <TagsInput.Root
      validate={(details) => {
        return !details.value.includes(details.inputValue)
      }}
    >
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Blur behavior

When the tags input is blurred, you can configure the action the component should take by passing the blurBehavior prop.

  • add — Adds the tag to the list of tags.
  • clear — Clears the tags input value.
import { TagsInput } from '@ark-ui/react'

const BlurBehavior = () => {
  return (
    <TagsInput.Root blurBehavior="add">
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Paste behavior

To add a tag when a arbitrary value is pasted in the input element, pass the addOnPaste prop.

When a value is pasted, the component will:

  • check if the value is a valid tag based on the validate option
  • split the value by the delimiter option passed
import { TagsInput } from '@ark-ui/react'

const PasteBehavior = () => {
  return (
    <TagsInput.Root addOnPaste delimiter=",">
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Disable tag editing

by default the tags can be edited by double-clicking on the tag or focusing on them and pressing Enter. To disable this behavior, pass allowEditTag={false}

import { TagsInput } from '@ark-ui/react'

const DisabledEditing = () => {
  return (
    <TagsInput.Root allowEditTag={false}>
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Events

During the lifetime of the tags input interaction, here’s a list of events we emit:

  • onValueChange — invoked when the tag value changes.
  • onHighlightChange — invoked when a tag has visual focus.
  • onValueInvalid — invoked when the max tag count is reached or the validate function returns false.
import { TagsInput } from '@ark-ui/react'

const OnEvent = () => {
  return (
    <TagsInput.Root
      onValueChange={(details) => {
        console.log('tags changed to:', details.value)
      }}
      onHighlightChange={(details) => {
        console.log('highlighted tag:', details.highlightedValue)
      }}
      onValueInvalid={(details) => {
        console.log('Invalid!', details.reason)
      }}
      max={3}
      allowOverflow
      validate={(details) => {
        return !details.value.includes(details.inputValue)
      }}
    >
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            {api.value.map((value, index) => (
              <TagsInput.Item key={index} index={index} value={value}>
                <TagsInput.ItemInput />
                <TagsInput.ItemText>{value}</TagsInput.ItemText>
                <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
              </TagsInput.Item>
            ))}
          </TagsInput.Control>
          <TagsInput.Input placeholder="Add Framework" />
          <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

API Reference

Root

PropTypeDefault
addOnPaste
boolean
allowEditTag
boolean
allowOverflow
boolean
asChild
boolean
autoFocus
boolean
blurBehavior
'clear' | 'add'"none"
defaultValue
string[]
delimiter
string"," (aka COMMA)
dir
'ltr' | 'rtl'"ltr"
disabled
boolean
form
string
getRootNode
() => Node | ShadowRoot | Document
id
string
ids
Partial<{ root: string input: string clearBtn: string label: string control: string item(opts: ItemProps): string itemDeleteTrigger(opts: ItemProps): string itemInput(opts: ItemProps): string }>
inputValue
string
invalid
boolean
max
number
maxLength
number
name
string
onFocusOutside
(event: FocusOutsideEvent) => void
onHighlightChange
(details: HighlightChangeDetails) => void
onInteractOutside
(event: InteractOutsideEvent) => void
onPointerDownOutside
(event: PointerDownOutsideEvent) => void
onValueChange
(details: ValueChangeDetails) => void
onValueInvalid
(details: ValidityChangeDetails) => void
readOnly
boolean
translations
IntlTranslations
validate
(details: ValidateArgs) => boolean
value
string[]

Item

PropTypeDefault
index
string | number
value
string
asChild
boolean
disabled
boolean

Input

PropTypeDefault
asChild
boolean

Label

PropTypeDefault
asChild
boolean

Control

PropTypeDefault
asChild
boolean

ItemText

PropTypeDefault
asChild
boolean

ItemInput

PropTypeDefault
asChild
boolean

ClearTrigger

PropTypeDefault
asChild
boolean

ItemDeleteTrigger

PropTypeDefault
asChild
boolean

Previous

Tabs

Next

Toast