Skip to content

Tags Input

Tag inputs render tags inside an input, followed by an actual text input.
Apple
Banana
vue
<script setup lang="ts">
import { ref } from 'vue'
import { TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from 'radix-vue'
import { Icon } from '@iconify/vue'

const modelValue = ref(['Apple', 'Banana'])
</script>

<template>
  <TagsInputRoot
    v-model="modelValue"
    class="flex gap-2 items-center border p-2 rounded-lg w-full max-w-[480px] flex-wrap border-blackA7 bg-white"
  >
    <TagsInputItem v-for="item in modelValue" :key="item" :value="item" class="text-white flex shadow-md items-center justify-center gap-2 bg-green8 aria-[current=true]:bg-green9 rounded p-1">
      <TagsInputItemText class="text-sm pl-1" />
      <TagsInputItemDelete class="p-0.5 rounded bg-transparent hover:bg-blackA4">
        <Icon icon="lucide:x" />
      </TagsInputItemDelete>
    </TagsInputItem>

    <TagsInputInput placeholder="Fruits..." class="text-sm focus:outline-none flex-1 rounded text-green9 bg-transparent placeholder:text-mauve9 px-1" />
  </TagsInputRoot>
</template>

Features

  • Can be controlled or uncontrolled.
  • Full keyboard navigation.
  • Limit the number of tags.
  • Accept value from clipboard.
  • Clear button to reset all tags values.

Installation

Install the component from your command line.

bash
npm install radix-vue

Anatomy

Import all parts and piece them together.

vue
<script setup>
import { TagsInputClear, TagsInputDelete, TagsInputInput, TagsInputItem, TagsInputRoot, TagsInputText } from 'radix-vue'
</script>

<template>
  <TagsInputRoot>
    <TagsInputItem>
      <TagsInputItemText />
      <TagsInputItemDelete />
    </TagsInputItem>

    <TagsInputInput />
    <TagsInputClear />
  </TagsInputRoot>
</template>

API Reference

Root

Contains all the tags input component parts.

PropTypeDefault
defaultValue
string
modelValue
string
addOnPaste
boolean
delimiter
string
, (comma)
duplicate
boolean
false
dir
enum
disabled
boolean
false
max
number
required
boolean
name
string
as
string | Component
div
asChild
boolean
false
EmitType
@update:modelValue
(value: string) => void
@invalid
(value: string) => void
Data AttributeValue
[data-disabled]Present when disabled
[data-focused]Present when focus on input
[data-invalid]Present when input value is invalid

Item

The component that contains the tag.

PropTypeDefault
as
string | Component
div
asChild
boolean
false
disabled
boolean
false
value
string
Data AttributeValue
[data-state]"active" | "inactive"
[data-disabled]Present when disabled

ItemText

The textual part of the tag. Important for accessibility.

PropTypeDefault
as
string | Component
span
asChild
boolean
false

ItemDelete

The button that delete the associate tag.

PropTypeDefault
as
string | Component
button
asChild
boolean
false
Data AttributeValue
[data-state]"active" | "inactive"
[data-disabled]Present when disabled

Input

The input element for the tags input.

PropTypeDefault
as
string | Component
input
asChild
boolean
false
placeholder
string
autoFocus
boolean
maxLength
number
Data AttributeValue
[data-invalid]Present when input value is invalid

Clear

The button that remove all tags.

PropTypeDefault
as
string | Component
button
asChild
boolean
false
Data AttributeValue
[data-disabled]Present when disabled

Examples

With Combobox

You can compose Tags input together with Combobox.

Apple
vue
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ComboboxAnchor, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxRoot, ComboboxTrigger, ComboboxViewport, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from 'radix-vue'
import { Icon } from '@iconify/vue'

const searchTerm = ref('')
const values = ref(['Apple'])
const options = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']

watch(values, () => {
  searchTerm.value = ''
}, { deep: true })
</script>

<template>
  <ComboboxRoot
    v-model="values"
    v-model:search-term="searchTerm"
    multiple
    class="my-4 mx-auto relative"
  >
    <ComboboxAnchor class="w-[400px] inline-flex items-center justify-between rounded-lg p-2 text-[13px] leading-none  gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-grass9 outline-none">
      <TagsInputRoot
        v-slot="{ values: tags }"
        :model-value="values"
        delimiter=""
        class="flex gap-2 items-center rounded-lg flex-wrap"
      >
        <TagsInputItem
          v-for="item in tags" :key="item"
          :value="item"
          class="flex items-center justify-center gap-2 text-white bg-grass8 aria-[current=true]:bg-grass9 rounded px-2 py-1"
        >
          <TagsInputItemText class="text-sm" />
          <TagsInputItemDelete>
            <Icon icon="lucide:x" />
          </TagsInputItemDelete>
        </TagsInputItem>

        <ComboboxInput as-child>
          <TagsInputInput
            placeholder="Fruits..."
            class="focus:outline-none flex-1 rounded !bg-transparent  placeholder:text-mauve10 px-1"
            @keydown.enter.prevent
          />
        </ComboboxInput>
      </TagsInputRoot>

      <ComboboxTrigger>
        <Icon icon="radix-icons:chevron-down" class="h-4 w-4 text-grass11" />
      </ComboboxTrigger>
    </ComboboxAnchor>
    <ComboboxContent class="absolute z-10 w-full mt-2 bg-white overflow-hidden rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade">
      <ComboboxViewport class="p-[5px]">
        <ComboboxEmpty class="text-gray-400  text-xs font-medium text-center py-2" />

        <ComboboxGroup>
          <ComboboxLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
            Fruits
          </ComboboxLabel>

          <ComboboxItem
            v-for="(option, index) in options" :key="index"
            class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-grass8 data-[highlighted]:text-grass1"
            :value="option"
          >
            <ComboboxItemIndicator
              class="absolute left-0 w-[25px] inline-flex items-center justify-center"
            >
              <Icon icon="radix-icons:check" />
            </ComboboxItemIndicator>
            <span>
              {{ option }}
            </span>
          </ComboboxItem>
        </ComboboxGroup>
      </ComboboxViewport>
    </ComboboxContent>
  </ComboboxRoot>
</template>

Paste behavior

You can automatically add tags on paste by passing the add-on-paste prop.

vue
<script setup lang="ts">
import { TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from 'radix-vue'
</script>

<template>
  <TagsInputRoot v-model="modelValue" add-on-paste>

  </TagsInputRoot>
</template>

Accessibility

Keyboard Interactions

KeyDescription
Delete
When tag is active, remove it and set the tag on right active.
Backspace
When tag is active, remove it and set the tag on left active. If there are no tags to the left, either the next tags gets focus, or the input.
ArrowRight
Set the next tag active.
ArrowLeft
Set the previous tag active.
Home
Set the first tag active
End
Set the last tag active