<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>@import '../_colors/black-alpha.css';
@import '../_colors/mauve.css';
@import '../_colors/grass.css';
@scope (.akar-demo-TagGroup) {
/* reset */
button {
all: unset;
}
}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
yarn add akar
npm install akar
bun 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.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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 | |
disabled | booleanWhen | |
disallowEmptySelection | booleanWhen | |
escapeKeyBehavior | "clearSelection" | "none" | "clearSelection"Whether pressing Escape clears the selection. |
loop | true | booleanWhen |
modelValue | T | T[]The controlled value of the selected tag(s). Can be binded with | |
selectionMode | "none" | "single" | "multiple" | "none"The type of selection that is allowed. Tags are not selectable when |
| Event | Payload |
|---|---|
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) |
| 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.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringA string representation of the tag's contents, used for typeahead and as the tag's accessible name. Defaults to the text of | |
value* | TThe unique value of the tag. |
| Slot (default) | Payload |
|---|---|
selected | booleanWhether the tag is selected |
disabled | booleanWhether the tag is disabled |
| 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.
| Prop | Default | Type |
|---|---|---|
as | "span" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
ItemDelete
The button that removes its tag. It is labelled "Remove" followed by the tag's name; pass an aria-label to translate it.
| Prop | Default | Type |
|---|---|---|
as | "button" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen |
| 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. |