Tags Input

Create and manage a list of tags/chips.

hello
world
	<script lang="ts">
  import { TagsInput } from "bits-ui";
  import X from "phosphor-svelte/lib/X";
  import Pencil from "phosphor-svelte/lib/Pencil";
 
  let value = $state<string[]>(["hello", "world"]);
</script>
 
<div class="flex items-center justify-center">
  <TagsInput.Root bind:value class="flex flex-col gap-2" delimiters={[","]}>
    <div
      class="flex h-auto w-[400px] flex-col gap-4 rounded-card-sm border border-border-input bg-background p-4 text-sm placeholder:text-foreground-alt/50 focus-within:outline-none focus-within:ring-2 focus-within:ring-foreground focus-within:ring-offset-2 focus-within:ring-offset-background hover:border-dark-40 [&:has([data-invalid])]:border-destructive"
    >
      <TagsInput.List class="flex min-h-5 flex-wrap gap-1.5">
        {#each value as tag, index}
          <TagsInput.Tag value={tag} {index}>
            <TagsInput.TagContent
              class="flex items-center gap-3 divide-x divide-pink-300/50 rounded-[4px] bg-[#FCDAFE] text-[0.7rem] font-semibold leading-none text-[#2A266B] no-underline group-hover:no-underline"
            >
              <TagsInput.TagText class="py-1 pl-1.5">
                {tag}
              </TagsInput.TagText>
              <div class="flex items-center divide-x divide-pink-300/50">
                <TagsInput.TagEdit
                  class="flex items-center justify-center px-1 py-1 hover:bg-[#edc6f0]"
                >
                  <Pencil class="size-3" />
                </TagsInput.TagEdit>
                <TagsInput.TagRemove
                  class="flex items-center justify-center rounded-r-[4px] px-1 py-1 hover:bg-[#edc6f0]"
                >
                  <X class="size-3" />
                </TagsInput.TagRemove>
              </div>
            </TagsInput.TagContent>
            <TagsInput.TagEditInput
              class="inline-flex h-5 w-auto items-center rounded-sm border border-border-input bg-background px-2 text-sm placeholder:text-foreground-alt/50 hover:border-dark-40 focus:outline-none focus:ring-2 focus:ring-foreground focus:ring-offset-2 focus:ring-offset-background"
            />
          </TagsInput.Tag>
        {/each}
      </TagsInput.List>
      <TagsInput.Input
        class="focus-override bg-transparent focus:outline-none focus:ring-0 "
        placeholder="Add a tag..."
        aria-label="Add a tag"
        blurBehavior="add"
      />
    </div>
    <TagsInput.Clear
      class="inline-flex h-input w-full items-center justify-center rounded-input bg-muted text-[15px] font-medium shadow-mini transition-all hover:bg-dark-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-98"
    >
      Clear Tags
    </TagsInput.Clear>
  </TagsInput.Root>
</div>

Overview

The Tag Input component provides a flexible and customizable way to create and manage a list of tags/chips.

Key Features

  • Compound Component Structure: Offers a set of sub-components that work together to create a fully-featured tag input.
  • Accessibility: Built-in ARIA attributes and keyboard navigation support.
  • Customizable: Each sub-component can be styled and configured independently.
  • Custom Validation: Validate the tags input's value before adding them to the list.

Architecture

The Tags Input component is composed of several sub-components, each with a specific role:

  • Root: The main container component that manages the state and context for the tags input.
  • List: The container for the tag input's tags.
  • Tag: The container for a single tag within the tags input. Responsible for managing the tag's state and interactions
  • TagContent: The content of the tag, which typically includes the tag text and edit/remove buttons and does not include the edit input.
  • TagText: The text content of the tag.
  • TagRemove: The button responsible for removing the tag.
  • TagEdit: The input element which should be displayed in place of the tag when it is being edited.
  • Input: The main input field for the tags input, which can be used to add new tags.
  • Clear: A button used to clear all tags from the tags input.

Structure

Here's an overview of how the Tags Input component is structured in code:

	<script lang="ts">
	import { TagsInput } from "bits-ui";
</script>
 
<TagsInput.Root>
	<TagsInput.List>
		<TagsInput.Tag>
			<TagsInput.TagContent>
				<TagsInput.TagText />
				<TagsInput.TagRemove />
			</TagsInput.TagContent>
			<TagsInput.TagEdit />
		</TagsInput.Tag>
	</TagsInput.List>
	<TagsInput.Input />
	<TagsInput.Clear />
</TagsInput.Root>

Managing Value State

Bits UI offers several approaches to manage and synchronize the Tag Input's value state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:value directive. This method automatically keeps your local state in sync with the component's internal state.

	<script lang="ts">
	import { TagsInput } from "bits-ui";
	let myValue = $state([]);
</script>
 
<button onclick={() => (myValue = ["a", "b"])}> Change value </button>
 
<TagsInput.Root bind:value={myValue}>
	<!-- ... -->
</TagsInput.Root>

Key Benefits

  • Simplifies state management
  • Automatically updates myValue when the internal state changes
  • Allows external control (e.g., updating the value via a separate button)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onValueChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import { TagsInput } from "bits-ui";
	let myValue = $state([]);
</script>
 
<TagsInput.Root
	value={myValue}
	onValueChange={(v) => {
		myValue = v;
		// additional logic here.
	}}
>
	<!-- ... -->
</TagsInput.Root>

Use Cases

  • Implementing custom behaviors on value change
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the component's value state, use the controlledValue prop. This approach requires you to manually manage the value state, giving you full control over when and how the component responds to value change events.

To implement controlled state:

  1. Set the controlledValue prop to true on the TagsInput.Root component.
  2. Provide a value prop to TagsInput.Root, which should be a variable holding the current state.
  3. Implement an onValueChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { TagsInput } from "bits-ui";
	let myValue = $state([]);
</script>
 
<TagsInput.Root controlledValue value={myValue} onValueChange={(v) => (myValue = v)}>
	<!-- ... -->
</TagsInput.Root>

When to Use

  • Implementing complex logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

Delimiters

Delimiters are used to split the input value into multiple tags when a user types them or pastes them into the input.

By default, a single delimiter is used ',', so when a user types or pastes a,b,c, the input value will be split into three tags: a, b, and c.

You can customize the delimiters used by setting the delimiters prop on the TagsInput.Root component.

	<TagsInput.Root delimiters={[",", ";", ":"]}>
	<!-- ... -->
</TagsInput.Root>

Validation

You can use the validate prop to validate the tag input's value before adding it to the list. The validate prop should return true if the value is valid, and false if it is invalid.

This single function enables you to validate a number of different scenarios, such as ensuring the value is not empty, ensuring it doesn't contain any invalid characters, or ensuring it doesn't exceed a certain length.

The following examples show how you might use the validate prop to implement these scenarios.

Maximum Tags

The validate prop can also be used to limit the maximum number of tags that can be added to the list.

	<script lang="ts">
	import { TagsInput } from "bits-ui";
	let value = $state<string[]>([]);
	const maxTags = 3;
</script>
 
<TagsInput.Root bind:value validate={(value) => value.length <= maxTags}>
	<!-- ... -->
</TagsInput.Root>

Prevent Duplicate Tags

You can also use the validate prop to prevent duplicate tags from being added to the list.

	<script lang="ts">
	import { TagsInput } from "bits-ui";
	let myValue = $state<string[]>([]);
</script>
 
<TagsInput.Root bind:myValue validate={(value) => !myValue.includes(value)}>
	<!-- ... -->
</TagsInput.Root>

Edit Modes

The editMode prop on the TagsInput.Tag component determines how the tag is editable. It can be set to one of the following values: 'input', 'contenteditable', or 'none'. The default value is 'input'.

  • 'input': the tag will be editable using the TagsInput.TagEdit component, which is an input element.
  • 'contenteditable': the tag will be editable using the contenteditable attribute on the TagsInput.TagText component.
  • 'none': the tag will not be editable.

Examples

Input

The main demo at the top of this page shows an example of the default editMode of 'input'.

Content Editable

The demo below shows an example of the editMode of 'contenteditable'. Try double-clicking on a tag to edit it.

hello
world
	<script lang="ts">
  import { TagsInput } from "bits-ui";
  import X from "phosphor-svelte/lib/X";
  import Pencil from "phosphor-svelte/lib/Pencil";
 
  let value = $state<string[]>(["hello", "world"]);
</script>
 
<div class="flex items-center justify-center">
  <TagsInput.Root bind:value class="flex flex-col gap-2" delimiters={[","]}>
    <div
      class="flex h-auto w-[400px] flex-col gap-4 rounded-card-sm border border-border-input bg-background p-4 text-sm placeholder:text-foreground-alt/50 focus-within:outline-none focus-within:ring-2 focus-within:ring-foreground focus-within:ring-offset-2 focus-within:ring-offset-background hover:border-dark-40 [&:has([data-invalid])]:border-destructive"
    >
      <TagsInput.List class="flex min-h-5 flex-wrap gap-1.5">
        {#each value as tag, index}
          <TagsInput.Tag value={tag} {index} editMode="contenteditable">
            <TagsInput.TagContent
              class="flex items-center gap-3 divide-x divide-pink-300/50 rounded-[4px] bg-[#FCDAFE] text-[0.7rem] font-semibold leading-none text-[#2A266B] no-underline group-hover:no-underline"
            >
              <TagsInput.TagText class="py-1 pl-1.5">
                {tag}
              </TagsInput.TagText>
              <div class="flex items-center divide-x divide-pink-300/50">
                <TagsInput.TagEdit
                  class="flex items-center justify-center px-1 py-1 hover:bg-[#edc6f0]"
                >
                  <Pencil class="size-3" />
                </TagsInput.TagEdit>
                <TagsInput.TagRemove
                  class="flex items-center justify-center rounded-r-[4px] px-1 py-1 hover:bg-[#edc6f0]"
                >
                  <X class="size-3" />
                </TagsInput.TagRemove>
              </div>
            </TagsInput.TagContent>
          </TagsInput.Tag>
        {/each}
      </TagsInput.List>
      <TagsInput.Input
        class="focus-override bg-transparent focus:outline-none focus:ring-0 "
        placeholder="Add a tag..."
        aria-label="Add a tag"
        blurBehavior="add"
      />
    </div>
    <TagsInput.Clear
      class="inline-flex h-input w-full items-center justify-center rounded-input bg-muted text-[15px] font-medium shadow-mini transition-all hover:bg-dark-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-98"
    >
      Clear Tags
    </TagsInput.Clear>
  </TagsInput.Root>
</div>

Blur Behavior

The blurBehavior prop determines how the input field is handled when it is blurred. It can be set to one of the following values: 'clear', 'add', or 'none'. The default value is 'none'.

  • 'none': the input field will not be cleared or modified when it is blurred, it will behave as an input normally would.
  • 'clear': the input field will be cleared when it is blurred.
  • 'add': the input field will be cleared and the value will be added as a new tag when it is blurred.

Paste Behavior

The pasteBehavior prop determines how the input field is handled when text is pasted into it.

It can be set to one of the following values: 'add' or 'none'.

  • 'add': the pasted text will be added as a new tag when it is pasted into the input field.
  • 'none': the pasted text will not be automatically added as a new tag when it is pasted into the input field.

API Reference

TagsInput.Root

An enhanced label component that can be used with any input.

Property Type Description
value $bindable
string[]

The value of the tags input. An array of tag values/strings.

Default: []
onValueChange
function

A callback function called when the active accordion item value changes. If the type is 'single', the argument will be a string. If type is 'multiple', the argument will be an array of strings.

Default: undefined
controlledValue
boolean

Whether or not the value is controlled or not. If true, the component will not update the value state internally, instead it will call onValueChange when it would have otherwise, and it is up to you to update the value prop that is passed to the component. See Controlled State for more information.

Default: false
delimiters
string[]

An array of delimiters to use to splitting the input value.

Default: [',']
validate
function

A validation function to determine if the individual tag being added/edited is valid. Return true to allow the tag to be added/edited, or false to prevent it from being added/confirm edited.

Default: undefined
name
string

If provided, a hidden input element will be rendered for each tag to submit the values with a form.

Default: undefined
required
boolean

Whether or not the hidden input element should be marked as required or not.

Default: false
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tags-input-root
''

Present on the root element.

TagsInput.List

The container for the tags.

Property Type Description
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-invalid
''

Present on the list element when the tags input is invalid.

data-tags-input-list
''

Present on the list element.

TagsInput.Tag

The container for a single tag within the tags input.

Property Type Description
value required
string

The value of the tag.

Default: undefined
index required
number

The index of the tag in the value array.

Default: undefined
editMode
enum

The edit mode of the tag.

Default: 'input'
removable
boolean

Whether or not the tag is removable or not.

Default: true
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-editing
''

Present on the tag element when the tag is being edited.

data-invalid
''

Present on the tag element when the tags input is marked as invalid.

data-editable
''

Present on the tag element when the tag is editable.

data-removable
''

Present on the tag element when the tag is removable.

data-tags-input-tag
''

Present on the tag element.

TagsInput.TagContent

The container for the tag content, which typically includes the tag text and edit/remove buttons and does not include the edit input.

Property Type Description
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tags-input-tag-content
''

TagsInput.TagText

The text content of the tag.

Property Type Description
ref $bindable
HTMLSpanElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tags-input-tag-text
''

Present on the text element.

data-editable
''

Present when the tag is editable.

data-removable
''

Present when the tag is removable.

TagsInput.TagRemove

The remove button for the tag.

Property Type Description
ref $bindable
HTMLButtonElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tags-input-tag-remove
''

Present on the remove element.

data-editable
''

Present when the tag is editable.

data-removable
''

Present when the tag is removable.

TagsInput.TagEditInput

The input that will receive focus when the tag is edited and the editMode is 'input'.

Property Type Description
ref $bindable
HTMLInputElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tags-input-tag-edit
''

Present on the edit element.

data-invalid
''

Present on the edit element when the tags input is marked as invalid.

data-editable
''

Present when the tag is editable.

data-removable
''

Present when the tag is removable.

TagsInput.Input

The input field for the tags input.

Property Type Description
blurBehavior
enum

The behavior to use when the input is blurred with text in it.

Default: 'none'
pasteBehavior
enum

How text is handled when it is pasted into the input.

Default: 'add'
value $bindable
string

The value of the input.

Default: undefined
onValueChange
function

A callback function called when the value of the input changes.

Default: undefined
controlledValue
boolean

Whether or not the value is controlled or not. If true, the component will not update the value internally, instead it will call onValueChange when it would have otherwise, and it is up to you to update the value prop that is passed to the component.

Default: false
ref $bindable
HTMLInputElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-invalid
''

Present on the input element when the tags input is marked as invalid.

data-tags-input-input
''

Present on the input element.

TagsInput.Clear

The clear button for the tags input.

Property Type Description
ref $bindable
HTMLButtonElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-tags-input-clear
''

Present on the clear element.