Tags Input
A component that allows users to add tags to an input field.
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/tags-input'
export const Basic = () => {
return (
<TagsInput.Root>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{tagsInput.value.map((value, index) => (
<TagsInput.Item key={index} index={index} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
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>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
</script>
<template>
<TagsInput.Root>
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
Navigating and Editing tags
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/tags-input'
export const InitialValue = () => {
return (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']}>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{tagsInput.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.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
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>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue'])
</script>
<template>
<TagsInput.Root v-model="frameworks">
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemInput />
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
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/tags-input'
export const MaxWithOverflow = () => {
return (
<TagsInput.Root max={3} allowOverflow>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{tagsInput.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.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
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>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
</script>
<template>
<TagsInput.Root :max="3" allowOverflow>
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemInput />
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
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/tags-input'
export const Validated = () => {
return (
<TagsInput.Root
validate={(details) => {
return !details.value.includes(details.inputValue)
}}
>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{tagsInput.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.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
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>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
</script>
<template>
<TagsInput.Root
:validate="
(details) => {
return !details.value.includes(details.inputValue)
}
"
>
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemInput />
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
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/tags-input'
export const BlurBehavior = () => {
return (
<TagsInput.Root blurBehavior="add">
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{tagsInput.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.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
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>
)
}
<TagsInput.HiddenInput />
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
</script>
<template>
<TagsInput.Root blurBehavior="add">
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemInput />
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
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
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}
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 thevalidate
function returnsfalse
.
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/react/field'
import { TagsInput } from '@ark-ui/react/tags-input'
export const WithField = (props: Field.RootProps) => {
return (
<Field.Root {...props}>
<TagsInput.Root>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Label</TagsInput.Label>
<TagsInput.Control>
{tagsInput.value.map((value, index) => (
<TagsInput.Item key={index} index={index} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<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>
)
}
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>
)
}
<script setup lang="ts">
import { Field } from '@ark-ui/vue/field'
import { TagsInput } from '@ark-ui/vue/tags-input'
</script>
<template>
<Field.Root>
<TagsInput.Root>
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Label</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
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/react/tags-input'
export const RootProvider = () => {
const tagsInput = useTagsInput()
return (
<>
<button onClick={() => tagsInput.focus()}>Focus</button>
<TagsInput.RootProvider value={tagsInput}>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{tagsInput.value.map((value, index) => (
<TagsInput.Item key={index} index={index} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</>
)
}
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>
</>
)
}
<script setup lang="ts">
import { TagsInput, useTagsInput } from '@ark-ui/vue/tags-input'
const tagsInput = useTagsInput()
</script>
<template>
<button @click="tagsInput.focus()">Focus</button>
<TagsInput.RootProvider :value="tagsInput">
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item
v-for="(value, index) in tagsInput.value"
:key="index"
:index="index"
:value="value"
>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>Delete</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>Clear all</TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</template>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Accessibility
Keyboard Support
Key | Description |
---|---|
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 |