Components
Tags input

Tags Input

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

React
Solid
Vue

You can explore the tags-input component in the following curated examples.

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/solid/tags-input'
import { Index } from 'solid-js'

export const Basic = () => (
  <TagsInput.Root>
    <TagsInput.Context>
      {(api) => (
        <>
          <TagsInput.Label>Frameworks</TagsInput.Label>
          <TagsInput.Control>
            <Index each={api().value}>
              {(value, index) => (
                <TagsInput.Item index={index} value={value()}>
                  <TagsInput.ItemPreview>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.ItemPreview>
                  <TagsInput.ItemInput />
                </TagsInput.Item>
              )}
            </Index>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear All</TagsInput.ClearTrigger>
          </TagsInput.Control>
        </>
      )}
    </TagsInput.Context>
    <TagsInput.HiddenInput />
  </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/solid/tags-input'
import { Index } from 'solid-js'

export const InitialValue = () => {
  return (
    <TagsInput.Root value={['React', 'Solid', 'Vue']}>
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add tag" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </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/solid/tags-input'
import { Index } from 'solid-js'

export const MaxWithOverflow = () => {
  return (
    <TagsInput.Root max={3} allowOverflow>
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </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/solid/tags-input'
import { Index } from 'solid-js'

export const Validated = () => {
  return (
    <TagsInput.Root
      validate={(details) => {
        return !details.value.includes(details.inputValue)
      }}
    >
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </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/solid/tags-input'
import { Index } from 'solid-js'

export const BlurBehavior = () => {
  return (
    <TagsInput.Root blurBehavior="add">
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </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/solid/tags-input'
import { Index } from 'solid-js'

export const PasteBehavior = () => {
  return (
    <TagsInput.Root addOnPaste delimiter=",">
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </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 editable={false}

import { TagsInput } from '@ark-ui/solid/tags-input'
import { Index } from 'solid-js'

export const DisabledEditing = () => {
  return (
    <TagsInput.Root editable={false}>
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </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/solid/tags-input'
import { Index } from 'solid-js'

export 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)
      }}
    >
      <TagsInput.Context>
        {(api) => (
          <>
            <TagsInput.Label>Frameworks</TagsInput.Label>
            <TagsInput.Control>
              <Index each={api().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                    <TagsInput.ItemInput />
                    <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                  </TagsInput.Item>
                )}
              </Index>
            </TagsInput.Control>
            <TagsInput.Input placeholder="Add Framework" />
            <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
          </>
        )}
      </TagsInput.Context>
      <TagsInput.HiddenInput />
    </TagsInput.Root>
  )
}

Using the Field Component

The Field component helps manage form-related state and accessibility attributes of a tags input. It includes handling ARIA labels, helper text, and error text to ensure proper accessibility.

import { Field } from '@ark-ui/solid/field'
import { TagsInput } from '@ark-ui/solid/tags-input'
import { Index } from 'solid-js'

export const WithField = (props: Field.RootProps) => {
  return (
    <Field.Root {...props}>
      <TagsInput.Root>
        <TagsInput.Context>
          {(tagsInput) => (
            <>
              <TagsInput.Label>Label</TagsInput.Label>
              <Index each={tagsInput().value}>
                {(value, index) => (
                  <TagsInput.Item index={index} value={value()}>
                    <TagsInput.ItemPreview>
                      <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                      <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                    </TagsInput.ItemPreview>
                    <TagsInput.ItemInput />
                  </TagsInput.Item>
                )}
              </Index>
              <TagsInput.Input placeholder="Add Framework" />
              <TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
            </>
          )}
        </TagsInput.Context>
        <TagsInput.HiddenInput />
      </TagsInput.Root>
      <Field.HelperText>Additional Info</Field.HelperText>
      <Field.ErrorText>Error Info</Field.ErrorText>
    </Field.Root>
  )
}

Using the Root Provider

The RootProvider component provides a context for the tags-input. It accepts the value of the useTags-input hook. You can leverage it to access the component state and methods from outside the tags-input.

import { TagsInput, useTagsInput } from '@ark-ui/solid/tags-input'
import { Index } from 'solid-js'

export const RootProvider = () => {
  const tagsInput = useTagsInput()

  return (
    <>
      <button onClick={() => tagsInput().focus()}>Focus</button>

      <TagsInput.RootProvider value={tagsInput}>
        <TagsInput.Context>
          {(api) => (
            <>
              <TagsInput.Label>Frameworks</TagsInput.Label>
              <TagsInput.Control>
                <Index each={api().value}>
                  {(value, index) => (
                    <TagsInput.Item index={index} value={value()}>
                      <TagsInput.ItemPreview>
                        <TagsInput.ItemText>{value()}</TagsInput.ItemText>
                        <TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
                      </TagsInput.ItemPreview>
                      <TagsInput.ItemInput />
                    </TagsInput.Item>
                  )}
                </Index>
                <TagsInput.Input placeholder="Add Framework" />
                <TagsInput.ClearTrigger>Clear All</TagsInput.ClearTrigger>
              </TagsInput.Control>
            </>
          )}
        </TagsInput.Context>
        <TagsInput.HiddenInput />
      </TagsInput.RootProvider>
    </>
  )
}

If you're using the RootProvider component, you don't need to use the Root component.

API Reference

Root

PropDefaultType
addOnPastefalse
boolean

Whether to add a tag when you paste values into the tag input

allowOverflow
boolean

Whether to allow tags to exceed max. In this case, we'll attach `data-invalid` to the root

asChild
(props: ParentProps<'div'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
autoFocus
boolean

Whether the input should be auto-focused

blurBehavior
'clear' | 'add'

The behavior of the tags input when the input is blurred - `"add"`: add the input value as a new tag - `"clear"`: clear the input value

defaultValue
string[]

The initial value of the tags input when it is first rendered. Use when you do not need to control the state of the tags input.

delimiter','
string | RegExp

The character that serves has: - event key to trigger the addition of a new tag - character used to split tags when pasting into the input

disabled
boolean

Whether the tags input should be disabled

editabletrue
boolean

Whether a tag can be edited after creation, by presing `Enter` or double clicking.

form
string

The associate form of the underlying input element.

ids
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 }>

The ids of the elements in the tags input. Useful for composition.

inputValue
string

The tag input's value

invalid
boolean

Whether the tags input is invalid

maxInfinity
number

The max number of tags

maxLength
number

The max length of the input.

name
string

The name attribute for the input. Useful for form submissions

onFocusOutside
(event: FocusOutsideEvent) => void

Function called when the focus is moved outside the component

onHighlightChange
(details: HighlightChangeDetails) => void

Callback fired when a tag is highlighted by pointer or keyboard navigation

onInputValueChange
(details: InputValueChangeDetails) => void

Callback fired when the input value is updated

onInteractOutside
(event: InteractOutsideEvent) => void

Function called when an interaction happens outside the component

onPointerDownOutside
(event: PointerDownOutsideEvent) => void

Function called when the pointer is pressed down outside the component

onValueChange
(details: ValueChangeDetails) => void

Callback fired when the tag values is updated

onValueInvalid
(details: ValidityChangeDetails) => void

Callback fired when the max tag count is reached or the `validateTag` function returns `false`

readOnly
boolean

Whether the tags input should be read-only

required
boolean

Whether the tags input is required

translations
IntlTranslations

Specifies the localized strings that identifies the accessibility elements and their states

validate
(details: ValidateArgs) => boolean

Returns a boolean that determines whether a tag can be added. Useful for preventing duplicates or invalid tag values.

value
string[]

The tag values

Data AttributeValue
[data-scope]tags-input
[data-part]root
[data-invalid]Present when invalid
[data-readonly]Present when read-only
[data-disabled]Present when disabled
[data-focus]Present when focused
[data-empty]

ClearTrigger

PropDefaultType
asChild
(props: ParentProps<'button'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]tags-input
[data-part]clear-trigger
[data-readonly]Present when read-only

Control

PropDefaultType
asChild
(props: ParentProps<'div'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]tags-input
[data-part]control
[data-disabled]Present when disabled
[data-readonly]Present when read-only
[data-invalid]Present when invalid
[data-focus]Present when focused

HiddenInput

PropDefaultType
asChild
(props: ParentProps<'input'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

Input

PropDefaultType
asChild
(props: ParentProps<'input'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]tags-input
[data-part]input
[data-invalid]Present when invalid
[data-readonly]Present when read-only

ItemDeleteTrigger

PropDefaultType
asChild
(props: ParentProps<'button'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

ItemInput

PropDefaultType
asChild
(props: ParentProps<'input'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

ItemPreview

PropDefaultType
asChild
(props: ParentProps<'div'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]tags-input
[data-part]item-preview
[data-value]The value of the item
[data-disabled]Present when disabled
[data-highlighted]Present when highlighted

Item

PropDefaultType
index
string | number

value
string

asChild
(props: ParentProps<'div'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
disabled
boolean

Data AttributeValue
[data-scope]tags-input
[data-part]item
[data-value]The value of the item
[data-disabled]Present when disabled

ItemText

PropDefaultType
asChild
(props: ParentProps<'span'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]tags-input
[data-part]item-text
[data-disabled]Present when disabled
[data-highlighted]Present when highlighted

Label

PropDefaultType
asChild
(props: ParentProps<'label'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]tags-input
[data-part]label
[data-disabled]Present when disabled
[data-invalid]Present when invalid
[data-readonly]Present when read-only

RootProvider

PropDefaultType
value
UseTagsInputReturn

asChild
(props: ParentProps<'div'>) => Element

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

Accessibility

Keyboard Support

KeyDescription
ArrowLeft
Moves focus to the previous tag item
ArrowRight
Moves focus to the next tag item
Backspace
Deletes the tag item that has visual focus or the last tag item
Enter
When a tag item has visual focus, it puts the tag in edit mode.
When the input has focus, it adds the value to the list of tags
Delete
Deletes the tag item that has visual focus
Control + V
When `addOnPaste` is set. Adds the pasted value as a tags