Tag Group

GitHubnpm
A focusable list of tags that can be navigated, selected and removed with the keyboard.
Alpha
Apple
Banana
Blueberry
Grape
<script setup lang="ts">
import { TagGroupItem, TagGroupItemDelete, TagGroupItemText, TagGroupRoot } from 'akar';
import { ref } from 'vue';

const tags = ref(['Apple', 'Banana', 'Blueberry', 'Grape']);
const selected = ref<Array<string>>(['Banana']);

function onRemove(values: Array<string>) {
  tags.value = tags.value.filter((tag) => !values.includes(tag));
}
</script>

<template>
  <div class="akar-demo akar-demo-TagGroup">
    <TagGroupRoot
      v-model="selected"
      selection-mode="multiple"
      aria-label="Fruits"
      class="flex flex-wrap gap-2 w-[380px] items-center"
      @remove="onRemove"
    >
      <TagGroupItem
        v-for="tag in tags"
        :key="tag"
        :value="tag"
        class="color-grass11 data-[state=checked]:bg-grass9 focus-visible:ring-grass7 text-sm font-medium px-2 py-1 outline-none rounded-lg bg-white flex gap-1.5 cursor-default shadow-md items-center justify-center data-[state=checked]:color-white data-[disabled]:opacity-50 focus-visible:ring-2"
      >
        <TagGroupItemText>{{ tag }}</TagGroupItemText>
        <TagGroupItemDelete
          class="hover:bg-blackA4 focus-visible:ring-grass7 p-0.5 rounded bg-transparent focus-visible:outline-none focus-visible:ring-2"
        >
          <PIcon
            name="i-lucide-x"
            class="size-3"
          />
        </TagGroupItemDelete>
      </TagGroupItem>
      <span
        v-if="!tags.length"
        class="color-mauve11 text-sm"
      >No fruits</span>
    </TagGroupRoot>
  </div>
</template>

Features

  • Full keyboard navigation, including typeahead.
  • Supports no, single or multiple selection.
  • Removable with the keyboard or a delete button.
  • Keeps focus on a neighbouring tag after removal.
  • Can be controlled or uncontrolled.
  • Labelled and described by an ancestor Field.

Installation

Install the component from your command line.

pnpm add akar

Anatomy

Import all parts and piece them together.

<script setup>
import { TagGroupItem, TagGroupItemDelete, TagGroupItemText, TagGroupRoot } from 'akar';
</script>

<template>
  <TagGroupRoot>
    <TagGroupItem>
      <TagGroupItemText />
      <TagGroupItemDelete />
    </TagGroupItem>
  </TagGroupRoot>
</template>

API Reference

Root

Contains all the tags. Renders a grid, or a group when there are no tags.

PropDefaultType
as
"div"
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

by
string | ((a: T, b: T) => boolean)

Use this to compare objects by a particular field, or pass your own comparison function for complete control over how objects are compared.

defaultValue
T | T[]

The value of the selected tag(s) when initially rendered. Use when you do not need to control the selection.

dir
"ltr" | "rtl"

The reading direction of the tag group when applicable.

If omitted, inherits globally from ConfigProvider or assumes LTR (left-to-right) reading mode.

disabled
boolean

When true, prevents the user from interacting with the tag group and all its tags.

disallowEmptySelection
boolean

When true, the user cannot deselect the last selected tag.

escapeKeyBehavior
"clearSelection"
"none" | "clearSelection"

Whether pressing Escape clears the selection.

loop
true
boolean

When true, keyboard navigation will loop from last tag to first, and vice versa.

modelValue
T | T[]

The controlled value of the selected tag(s). Can be binded with v-model. An array when selectionMode is multiple.

selectionMode
"none"
"single" | "multiple" | "none"

The type of selection that is allowed. Tags are not selectable when none.

EventPayload
remove
values: T[]

Event handler called when the user removes tags, with the values to remove. Tags are only removable when this event has a listener; remove the values from your own list to remove the tags.

update:modelValue
value: T | T[]

Event handler called when the selection changes.

Slot (default)Payload
modelValue
T | T[]

Current selected value(s)

TagGroupRoot.vue source

Data attribute Value
[data-disabled]
Present when disabled
[data-empty]
Present when there are no tags

Item

A tag. Pressing Delete or Backspace on it removes it.

PropDefaultType
as
"div"
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

disabled
boolean

When true, prevents the user from interacting with the tag.

textValue
string

A string representation of the tag's contents, used for typeahead and as the tag's accessible name. Defaults to the text of TagGroupItemText.

value*
T

The unique value of the tag.

Slot (default)Payload
selected
boolean

Whether the tag is selected

disabled
boolean

Whether the tag is disabled

TagGroupItem.vue source

Data attribute Value
[data-state]
"checked" | "unchecked"
[data-disabled]
Present when disabled

ItemText

The text of the tag. Used for typeahead unless the item has a textValue.

PropDefaultType
as
"span"
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

TagGroupItemText.vue source

ItemDelete

The button that removes its tag. It is labelled "Remove" followed by the tag's name; pass an aria-label to translate it.

PropDefaultType
as
"button"
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

disabled
boolean

When true, prevents the user from interacting with the delete button.

TagGroupItemDelete.vue source

Data attribute Value
[data-disabled]
Present when disabled

Examples

Removing tags

Tags are removable when you listen to the remove event. It receives the values to remove, which you remove from your own list. Removing a selected tag with the keyboard removes every selected tag.

<script setup>
import { ref } from 'vue'

const tags = ref(['News', 'Travel', 'Gaming'])
function onRemove(values) {
  tags.value = tags.value.filter(tag => !values.includes(tag))
}
</script>

<template>
  <TagGroupRoot aria-label="Categories" @remove="onRemove">
    <TagGroupItem v-for="tag in tags" :key="tag" :value="tag">
      <TagGroupItemText>{{ tag }}</TagGroupItemText>
      <TagGroupItemDelete>×</TagGroupItemDelete>
    </TagGroupItem>
  </TagGroupRoot>
</template>

Selection

Set selectionMode to single or multiple to make tags selectable with a click, Space or Enter. v-model holds the selected value, or an array of values when multiple.

<template>
  <TagGroupRoot v-model="selected" selection-mode="multiple" aria-label="Amenities">
    <TagGroupItem v-for="amenity in amenities" :key="amenity" :value="amenity">
      <TagGroupItemText>{{ amenity }}</TagGroupItemText>
    </TagGroupItem>
  </TagGroupRoot>
</template>

Empty state

When there are no tags, the root renders as a focusable group with a data-empty attribute. Render any empty content inside it.

<template>
  <TagGroupRoot aria-label="Categories" @remove="onRemove">
    <TagGroupItem v-for="tag in tags" :key="tag" :value="tag">
      <TagGroupItemText>{{ tag }}</TagGroupItemText>
    </TagGroupItem>
    <span v-if="!tags.length">No categories
  </TagGroupRoot>
</template>

With a label and description

Place the root in a FieldRoot to label and describe it.

<template>
  <FieldRoot>
    <FieldLabel :native-label="false">
      Categories
    </FieldLabel>
    <TagGroupRoot @remove="onRemove">
      <!-- ... -->
    </TagGroupRoot>
    <FieldDescription>Your selected categories.</FieldDescription>
  </FieldRoot>
</template>

Accessibility

Adheres to the Grid WAI-ARIA design pattern, with each tag a row. When using asChild on TagGroupItem, wrap the tag's content in an element with role="gridcell".

Keyboard Interactions

Key Description
Tab
Moves focus into the tag group, to the focused tag's delete button, then out of the tag group.
ArrowRight/ArrowDown
Moves focus to the next tag.
ArrowLeft/ArrowUp
Moves focus to the previous tag.
Home/End
Moves focus to the first or last tag.
Space/Enter
When selectable, toggles the focused tag's selection.
Delete/Backspace
Removes the focused tag, or every selected tag when the focused tag is selected.
Escape
Clears the selection.