Tags Input
A component that allows users to add tags to an input field.
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/react/tags-input'
import { XIcon } from 'lucide-react'
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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Initial tags
To set the initial tag values, set the defaultValue
prop.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
export const InitialValue = () => {
return (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue', 'Svelte']}>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add tag" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const InitialValue = () => {
return (
<TagsInput.Root value={['React', 'Solid', 'Vue', 'Svelte']}>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add tag" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue', 'Svelte'])
</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.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root value={['React', 'Svelte']}>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Max 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'
import { XIcon } from 'lucide-react'
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.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</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.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root max={3} allowOverflow>
<TagsInput.Label>Frameworks (Max 3, Overflow Allowed)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Controlled
Use the value
and onValueChange
props to programmatically control the tags input's state. This allows you to manage
the tags array externally and respond to changes.
import { TagsInput } from '@ark-ui/react/tags-input'
import { useState } from 'react'
import { XIcon } from 'lucide-react'
export const Controlled = () => {
const [value, setValue] = useState<string[]>(['vue', 'react'])
return (
<>
<p>Values: {value.join(', ')}</p>
<TagsInput.Root value={value} onValueChange={(details) => setValue(details.value)}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{api.value.map((value, index) => (
<TagsInput.Item key={index} index={index} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index, createSignal } from 'solid-js'
export const Controlled = () => {
const [value, setValue] = createSignal<string[]>(['vue', 'react'])
return (
<>
<p>Values: {value().join(', ')}</p>
<TagsInput.Root value={value()} onValueChange={(details) => setValue(details.value)}>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const value = ref<string[]>(['vue', 'react'])
</script>
<template>
<p>Values: {{ value }}</p>
<TagsInput.Root v-model="value">
<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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
let value = $state<string[]>(['React', 'Svelte'])
</script>
<TagsInput.Root bind:value>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
<div style="margin-top: 1rem;">
<p>Current tags: {value.join(', ')}</p>
</div>
Controlled Input Value
Use the inputValue
and onInputValueChange
props to control the text input field independently. This is useful for
clearing the input or pre-filling it programmatically.
import { TagsInput } from '@ark-ui/react/tags-input'
import { useState } from 'react'
import { XIcon } from 'lucide-react'
export const ControlledInputValue = () => {
const [inputValue, setInputValue] = useState('')
return (
<div>
<div style={{ display: 'flex', gap: '8px', marginBottom: '12px' }}>
<button type="button" onClick={() => setInputValue('React')}>
Set to "React"
</button>
<button type="button" onClick={() => setInputValue('')}>
Clear Input
</button>
<span style={{ fontSize: '14px', marginLeft: '8px' }}>Current input: "{inputValue}"</span>
</div>
<TagsInput.Root inputValue={inputValue} onInputValueChange={(details) => setInputValue(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.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</div>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index, createSignal } from 'solid-js'
export const ControlledInputValue = () => {
const [inputValue, setInputValue] = createSignal('')
return (
<div>
<div style={{ display: 'flex', gap: '8px', 'margin-bottom': '12px' }}>
<button type="button" onClick={() => setInputValue('React')}>
Set to "React"
</button>
<button type="button" onClick={() => setInputValue('')}>
Clear Input
</button>
<span style={{ 'font-size': '14px', 'margin-left': '8px' }}>Current input: "{inputValue()}"</span>
</div>
<TagsInput.Root inputValue={inputValue()} onInputValueChange={(details) => setInputValue(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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</div>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const inputValue = ref('')
</script>
<template>
<div>
<div style="display: flex; gap: 8px; margin-bottom: 12px">
<button type="button" @click="inputValue = 'React'">Set to "React"</button>
<button type="button" @click="inputValue = ''">Clear Input</button>
<span style="font-size: 14px; margin-left: 8px">Current input: "{{ inputValue }}"</span>
</div>
<TagsInput.Root :input-value="inputValue" @input-value-change="(details) => (inputValue = 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.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</div>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
let inputValue = $state('')
</script>
<div>
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
<button type="button" onclick={() => (inputValue = 'React')}>Set to "React"</button>
<button type="button" onclick={() => (inputValue = '')}>Clear Input</button>
<span style="font-size: 14px; margin-left: 8px;">Current input: "{inputValue}"</span>
</div>
<TagsInput.Root bind:inputValue>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
</div>
Custom Delimiter
Use the delimiter
prop with a regex pattern to specify multiple characters that can separate tags. By default, only
the Enter key creates tags.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
const DELIMITER_PATTERN = /[,;\s]/
export const Delimiter = () => {
return (
<TagsInput.Root delimiter={DELIMITER_PATTERN}>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks (separate with comma, semicolon, or space)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Type and use comma, semicolon, or space" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
const DELIMITER_PATTERN = /[,;\s]/
export const Delimiter = () => (
<TagsInput.Root delimiter={DELIMITER_PATTERN}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks (separate with comma, semicolon, or space)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Type and use comma, semicolon, or space" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
const DELIMITER_PATTERN = /[,;\s]/
</script>
<template>
<TagsInput.Root :delimiter="DELIMITER_PATTERN">
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks (separate with comma, semicolon, or space)</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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Type and use comma, semicolon, or space" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
const DELIMITER_PATTERN = /[,;\s]/
</script>
<TagsInput.Root delimiter={DELIMITER_PATTERN}>
<TagsInput.Label>Frameworks (separate with comma, semicolon, or space)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Type and use comma, semicolon, or space" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Disabled
Use the disabled
prop to make the tags input non-interactive. Users won't be able to add, remove, or edit tags.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
export const Disabled = () => {
return (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} disabled>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks (Disabled)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Cannot interact when disabled" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const Disabled = () => (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} disabled>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks (Disabled)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Cannot interact when disabled" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root :default-value="['React', 'Solid', 'Vue']" disabled>
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks (Disabled)</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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Cannot interact when disabled" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} disabled>
<TagsInput.Label>Frameworks (Disabled)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Cannot interact when disabled" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Invalid
Use the invalid
prop to mark the tags input as invalid for form validation purposes.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
export const Invalid = () => {
return (
<TagsInput.Root invalid>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const Invalid = () => (
<TagsInput.Root invalid>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root invalid>
<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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root invalid>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Max Tag Length
Use the maxLength
prop to limit the number of characters allowed per tag. This prevents users from creating overly
long tags.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
export const MaxTagLength = () => {
return (
<TagsInput.Root maxLength={10}>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks (max 10 characters per tag)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Max 10 characters" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const MaxTagLength = () => (
<TagsInput.Root maxLength={10}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks (max 10 characters per tag)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Max 10 characters" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root :max-length="10">
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks (max 10 characters per tag)</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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Max 10 characters" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root maxLength={10}>
<TagsInput.Label>Frameworks (max 10 characters per tag)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Max 10 characters" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.Root>
Programmatic Control
Use the useTagsInput
hook with RootProvider
to access the component's API methods like addValue()
, setValue()
,
and clearValue()
for full programmatic control.
import { TagsInput, useTagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
export const ProgrammaticControl = () => {
const tagsInput = useTagsInput()
return (
<>
<div style={{ display: 'flex', gap: '8px', marginBottom: '12px' }}>
<button type="button" onClick={() => tagsInput.addValue('React')}>
Add React
</button>
<button type="button" onClick={() => tagsInput.addValue('Solid')}>
Add Solid
</button>
<button type="button" onClick={() => tagsInput.setValue(['Vue', 'Svelte'])}>
Set to Vue & Svelte
</button>
<button type="button" onClick={() => tagsInput.clearValue()}>
Clear All
</button>
</div>
<TagsInput.RootProvider value={tagsInput}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks (Programmatic Control)</TagsInput.Label>
<TagsInput.Control>
{api.value.map((value, index) => (
<TagsInput.Item key={index} index={index} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</>
)
}
import { TagsInput, useTagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const ProgrammaticControl = () => {
const tagsInput = useTagsInput()
return (
<>
<div style={{ display: 'flex', gap: '8px', 'margin-bottom': '12px' }}>
<button type="button" onClick={() => tagsInput().addValue('React')}>
Add React
</button>
<button type="button" onClick={() => tagsInput().addValue('Solid')}>
Add Solid
</button>
<button type="button" onClick={() => tagsInput().setValue(['Vue', 'Svelte'])}>
Set to Vue & Svelte
</button>
<button type="button" onClick={() => tagsInput().clearValue()}>
Clear All
</button>
</div>
<TagsInput.RootProvider value={tagsInput}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks (Programmatic Control)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</>
)
}
<script setup lang="ts">
import { TagsInput, useTagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
const tagsInput = useTagsInput()
</script>
<template>
<div>
<div style="display: flex; gap: 8px; margin-bottom: 12px">
<button type="button" @click="tagsInput.addValue('React')">Add React</button>
<button type="button" @click="tagsInput.addValue('Solid')">Add Solid</button>
<button type="button" @click="tagsInput.setValue(['Vue', 'Svelte'])">Set to Vue & Svelte</button>
<button type="button" @click="tagsInput.clearValue()">Clear All</button>
</div>
<TagsInput.RootProvider :value="tagsInput">
<TagsInput.Context v-slot="api">
<TagsInput.Label>Frameworks (Programmatic Control)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Item v-for="(value, index) in api.value" :key="index" :index="index" :value="value">
<TagsInput.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</div>
</template>
<script lang="ts">
import { TagsInput, useTagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
const tags = useTagsInput()
</script>
<div>
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
<button type="button" onclick={() => tags().addValue('React')}>Add React</button>
<button type="button" onclick={() => tags().addValue('Solid')}>Add Solid</button>
<button type="button" onclick={() => tags().setValue(['Vue', 'Svelte'])}>
Set to Vue & Svelte
</button>
<button type="button" onclick={() => tags().clearValue()}>Clear All</button>
</div>
<TagsInput.RootProvider value={tags}>
<TagsInput.Label>Frameworks (Programmatic Control)</TagsInput.Label>
<TagsInput.Control>
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</div>
Read-only
Use the readOnly
prop to make tags visible but not editable. Users can view tags but cannot add, remove, or modify
them.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
export const Readonly = () => {
return (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} readOnly>
<TagsInput.Context>
{(tagsInput) => (
<>
<TagsInput.Label>Frameworks (Read-only)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Cannot add tags in read-only mode" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const Readonly = () => (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} readOnly>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks (Read-only)</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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Cannot add tags in read-only mode" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root :default-value="['React', 'Solid', 'Vue']" read-only>
<TagsInput.Context v-slot="tagsInput">
<TagsInput.Label>Frameworks (Read-only)</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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Cannot add tags in read-only mode" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} readOnly>
<TagsInput.Label>Frameworks (Read-only)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Cannot add tags in read-only mode" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<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/react/tags-input'
import { XIcon } from 'lucide-react'
const TAG_PATTERN = /^[a-zA-Z0-9-]+$/
const validateTag = (details: { value: string[]; inputValue: string }) => {
const { value, inputValue } = details
if (!inputValue || inputValue.trim() === '') {
return false
}
if (value.includes(inputValue)) {
return false
}
if (inputValue.length < 3) {
return false
}
if (!TAG_PATTERN.test(inputValue)) {
return false
}
return true
}
export const Validation = () => {
return (
<TagsInput.Root validate={validateTag}>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
<TagsInput.Input placeholder="Add Framework (min 3 chars, alphanumeric)" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const Validation = () => {
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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</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.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root
validate={(details) => {
return !details.value.includes(details.inputValue)
}}
>
<TagsInput.Context>
{#snippet render(tagsInput)}
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{#each tagsInput().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
{/snippet}
</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/react/tags-input'
import { XIcon } from 'lucide-react'
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.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</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.ItemPreview>
<TagsInput.ItemText>{{ value }}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root blurBehavior="add">
<TagsInput.Context>
{#snippet render(tagsInput)}
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{#each tagsInput().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
{/snippet}
</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/react/tags-input'
import { XIcon } from 'lucide-react'
export const PasteBehavior = () => {
return (
<TagsInput.Root addOnPaste delimiter=",">
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root addOnPaste delimiter=",">
<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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root addOnPaste delimiter=",">
<TagsInput.Context>
{#snippet render(tagsInput)}
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{#each tagsInput().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
{/snippet}
</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/react/tags-input'
import { XIcon } from 'lucide-react'
export const DisabledEditing = () => {
return (
<TagsInput.Root editable={false}>
<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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root :allowEditTag="false">
<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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<TagsInput.Root value={['React', 'Svelte', 'Vue']} editable={false}>
<TagsInput.Label>Frameworks (Read-only)</TagsInput.Label>
<TagsInput.Control>
<TagsInput.Context>
{#snippet render(tags)}
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
{/snippet}
</TagsInput.Context>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<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 thevalidate
function returnsfalse
.
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
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>
{(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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
import { TagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
)
}
<script setup lang="ts">
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
</script>
<template>
<TagsInput.Root
@value-change="(details) => console.log(details)"
@highlight-change="(details) => console.log(details)"
@value-invalid="(details) => console.log(details)"
>
<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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.Root>
</template>
<script lang="ts">
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<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>
{#snippet render(tagsInput)}
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{#each tagsInput().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
{/snippet}
</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/react/field'
import { TagsInput } from '@ark-ui/react/tags-input'
import { XIcon } from 'lucide-react'
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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</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 { XIcon } from 'lucide-solid'
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>
<TagsInput.Control>
<Index each={tagsInput().value}>
{(value, index) => (
<TagsInput.Item index={index} value={value()}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value()}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</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, type FieldRootProps } from '@ark-ui/vue/field'
import { TagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
const props = defineProps<FieldRootProps>()
</script>
<template>
<Field.Root v-bind="props">
<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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></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>
<script lang="ts">
import { Field } from '@ark-ui/svelte/field'
import { TagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
</script>
<Field.Root>
<TagsInput.Root>
<TagsInput.Context>
{#snippet render(tagsInput)}
<TagsInput.Label>Label</TagsInput.Label>
<TagsInput.Control>
{#each tagsInput().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
{/snippet}
</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/react/tags-input'
import { XIcon } from 'lucide-react'
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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
))}
</TagsInput.Control>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</>
)
}
import { TagsInput, useTagsInput } from '@ark-ui/solid/tags-input'
import { XIcon } from 'lucide-solid'
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>
<XIcon />
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
)}
</Index>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger>
<XIcon />
</TagsInput.ClearTrigger>
</TagsInput.Control>
</>
)}
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</>
)
}
<script setup lang="ts">
import { TagsInput, useTagsInput } from '@ark-ui/vue/tags-input'
import { XIcon } from 'lucide-vue-next'
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><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
<TagsInput.Input placeholder="Add Framework" />
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
</TagsInput.Control>
</TagsInput.Context>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</template>
<script lang="ts">
import { TagsInput, useTagsInput } from '@ark-ui/svelte/tags-input'
import { XIcon } from 'lucide-svelte'
const id = $props.id()
const tags = useTagsInput({
id,
defaultValue: ['React', 'Svelte'],
})
</script>
<div>
<button onclick={() => tags().addValue('Vue')}>Add Vue</button>
<button onclick={() => tags().clearValue()}>Clear All</button>
<TagsInput.RootProvider value={tags}>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{#each tags().value as value, index (index)}
<TagsInput.Item {index} {value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger><XIcon /></TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{/each}
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger><XIcon /></TagsInput.ClearTrigger>
<TagsInput.HiddenInput />
</TagsInput.RootProvider>
</div>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
Guides
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.
API Reference
Root
Prop | Default | Type |
---|---|---|
addOnPaste | false | 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 | boolean 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 | |
defaultInputValue | string The initial tag input value when rendered. Use when you don't need to control the tag input value. | |
defaultValue | string[] The initial tag value when rendered. Use when you don't need to control the tag value. | |
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 | |
editable | true | boolean Whether a tag can be edited after creation, by pressing `Enter` or double clicking. |
form | string The associate form of the underlying input element. | |
id | string The unique identifier of the machine. | |
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 controlled tag input's value | |
invalid | boolean Whether the tags input is invalid | |
max | Infinity | 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 controlled tag value |
Data Attribute | Value |
---|---|
[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] | Present when the content is empty |
ClearTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tags-input |
[data-part] | clear-trigger |
[data-readonly] | Present when read-only |
Control
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[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
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Input
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tags-input |
[data-part] | input |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
ItemDeleteTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ItemInput
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ItemPreview
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[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
Prop | Default | Type |
---|---|---|
index | string | number | |
value | string | |
asChild | boolean 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 Attribute | Value |
---|---|
[data-scope] | tags-input |
[data-part] | item |
[data-value] | The value of the item |
[data-disabled] | Present when disabled |
ItemText
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tags-input |
[data-part] | item-text |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
Label
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tags-input |
[data-part] | label |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseTagsInputReturn | |
asChild | boolean 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
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 |