Skip to content

TagGroup

Groups selectable or removable tags.

Import

ts
import { Tag, TagGroup, TagRemoveButton } from '@heroui-vue/vue'

Usage

Categories
News Travel Gaming Shopping

<template>
  <TagGroup label="Categories" selection-mode="single" :default-selected-keys="['travel']">
    <Tag value="news">
      <svg aria-hidden="true" fill="none" viewBox="0 0 24 24">
        <path d="M4 5h11a3 3 0 0 1 3 3v11H7a3 3 0 0 1-3-3V5Z" stroke="currentColor" stroke-linejoin="round" stroke-width="2" />
        <path d="M8 9h6M8 13h6" stroke="currentColor" stroke-linecap="round" stroke-width="2" />
      </svg>
      News
    </Tag>
    <Tag value="travel">
      <svg aria-hidden="true" fill="none" viewBox="0 0 24 24">
        <path d="M3 11.5 21 4l-7.5 18-3-7.5L3 11.5Z" stroke="currentColor" stroke-linejoin="round" stroke-width="2" />
      </svg>
      Travel
    </Tag>
    <Tag value="gaming">
      <svg aria-hidden="true" fill="none" viewBox="0 0 24 24">
        <path d="M7 10h10a4 4 0 0 1 3.7 2.5l.6 1.5a3 3 0 0 1-4.7 3.4L14 15H10l-2.6 2.4A3 3 0 0 1 2.7 14l.6-1.5A4 4 0 0 1 7 10Z" stroke="currentColor" stroke-linejoin="round" stroke-width="2" />
        <path d="M8 13h.01M16 13h.01" stroke="currentColor" stroke-linecap="round" stroke-width="2" />
      </svg>
      Gaming
    </Tag>
    <Tag value="shopping">
      <svg aria-hidden="true" fill="none" viewBox="0 0 24 24">
        <path d="M6 8h12l-1 12H7L6 8Z" stroke="currentColor" stroke-linejoin="round" stroke-width="2" />
        <path d="M9 8a3 3 0 0 1 6 0" stroke="currentColor" stroke-linecap="round" stroke-width="2" />
      </svg>
      Shopping
    </Tag>
  </TagGroup>
</template>

<script setup lang="ts">
import { Tag, TagGroup } from '@heroui-vue/vue'
</script>

Anatomy

vue
<TagGroup>
  <Tag>
    Label
    <TagRemoveButton />
  </Tag>
</TagGroup>

Sizes

Small
NewsTravelGaming
Medium
NewsTravelGaming
Large
NewsTravelGaming

<template>
  <div class="demo-tag-group-stack">
    <TagGroup label="Small" selection-mode="multiple" size="sm" :default-selected-keys="['news']">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
    </TagGroup>

    <TagGroup label="Medium" selection-mode="multiple" size="md" :default-selected-keys="['travel']">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
    </TagGroup>

    <TagGroup label="Large" selection-mode="multiple" size="lg" :default-selected-keys="['gaming']">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
    </TagGroup>
  </div>
</template>

<script setup lang="ts">
import { Tag, TagGroup } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-tag-group-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
}
</style>

Variants

Default
VueReactSvelte
Surface
VueReactSvelte

<template>
  <div class="demo-tag-group-stack">
    <TagGroup label="Default" selection-mode="multiple" :default-selected-keys="['vue']">
      <Tag value="vue">Vue</Tag>
      <Tag value="react">React</Tag>
      <Tag value="svelte">Svelte</Tag>
    </TagGroup>

    <TagGroup label="Surface" selection-mode="multiple" variant="surface" :default-selected-keys="['react']">
      <Tag value="vue">Vue</Tag>
      <Tag value="react">React</Tag>
      <Tag value="svelte">Svelte</Tag>
    </TagGroup>
  </div>
</template>

<script setup lang="ts">
import { Tag, TagGroup } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-tag-group-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
}
</style>

Disabled

Disabled keys
NewsTravelGamingShopping
Disabled group
NewsTravelGaming

<template>
  <div class="demo-tag-group-stack">
    <TagGroup
      label="Disabled keys"
      selection-mode="multiple"
      :default-selected-keys="['news']"
      :disabled-keys="['gaming', 'shopping']"
    >
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
      <Tag value="shopping">Shopping</Tag>
    </TagGroup>

    <TagGroup label="Disabled group" selection-mode="multiple" disabled :default-selected-keys="['travel']">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
    </TagGroup>
  </div>
</template>

<script setup lang="ts">
import { Tag, TagGroup } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-tag-group-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
}
</style>

Selection Modes

Single selection
NewsTravelGamingShopping
Multiple selection
NewsTravelGamingShopping

<template>
  <div class="demo-tag-group-stack">
    <TagGroup label="Single selection" selection-mode="single" :default-selected-keys="['travel']">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
      <Tag value="shopping">Shopping</Tag>
    </TagGroup>

    <TagGroup label="Multiple selection" selection-mode="multiple" :default-selected-keys="['news', 'gaming']">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
      <Tag value="shopping">Shopping</Tag>
    </TagGroup>
  </div>
</template>

<script setup lang="ts">
import { Tag, TagGroup } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-tag-group-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
}
</style>

Controlled

Interests
NewsTravelGamingShopping
Selected: travel, gaming

<template>
  <div class="demo-tag-group-controlled">
    <TagGroup v-model:selected-keys="selectedKeys" label="Interests" selection-mode="multiple">
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
      <Tag value="shopping">Shopping</Tag>
    </TagGroup>
    <Description>Selected: {{ selectedLabel }}</Description>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { Description, Tag, TagGroup } from '@heroui-vue/vue'

const selectedKeys = ref<Array<string | number>>(['travel', 'gaming'])
const selectedLabel = computed(() => selectedKeys.value.length ? selectedKeys.value.join(', ') : 'none')
</script>

<style lang="less">
.demo-tag-group-controlled {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}
</style>

With Error Message

Choose at least one topic
NewsTravelGamingShopping

<template>
  <form class="demo-tag-group-form" @submit.prevent="handleSubmit">
    <TagGroup
      v-model:selected-keys="selectedKeys"
      label="Choose at least one topic"
      selection-mode="multiple"
      :is-invalid="showError"
    >
      <Tag value="news">News</Tag>
      <Tag value="travel">Travel</Tag>
      <Tag value="gaming">Gaming</Tag>
      <Tag value="shopping">Shopping</Tag>
    </TagGroup>
    <FieldError v-if="showError">Select at least one topic.</FieldError>
    <Button type="submit">Submit</Button>
  </form>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { Button, FieldError, Tag, TagGroup } from '@heroui-vue/vue'

const selectedKeys = ref<Array<string | number>>([])
const showError = ref(false)

const handleSubmit = () => {
  showError.value = selectedKeys.value.length === 0
}

watch(selectedKeys, (keys) => {
  if (keys.length > 0) {
    showError.value = false
  }
})
</script>

<style lang="less">
.demo-tag-group-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
}
</style>

With Prefix

With Icons
News Travel Gaming Shopping
Tags with icons
With Avatars
FredF Fred MichaelM Michael JaneJ Jane
Tags with avatars

<template>
  <div class="demo-tag-group-prefix">
    <TagGroup label="With Icons" selection-mode="single">
      <Tag value="news">
        <svg aria-hidden="true" fill="none" viewBox="0 0 16 16">
          <path clip-rule="evenodd" d="M11.5 3h-7A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3m-7-1.5a3 3 0 0 0-3 3v7a3 3 0 0 0 3 3h7a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3zm6 6H5.43a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h5.07a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1m-5.32-3h3.57a.75.75 0 0 1 0 1.5H5.18a.75.75 0 0 1 0-1.5" fill="currentColor" fill-rule="evenodd" />
        </svg>
        News
      </Tag>
      <Tag value="travel">
        <svg aria-hidden="true" fill="none" viewBox="0 0 16 16">
          <path clip-rule="evenodd" d="M8 13.5c1.63 0 3.094-.709 4.101-1.835A2.5 2.5 0 0 1 10.25 9.25V9a.75.75 0 0 0-.75-.75 2.25 2.25 0 0 1 0-4.5.75.75 0 0 0 .75-.75v-.02a5.5 5.5 0 0 0-7.471 3.287A2.25 2.25 0 0 1 4.75 8.5c0 .414.336.75.75.75a2.25 2.25 0 0 1 1.265 4.11q.597.139 1.235.14m-3.491-1.25H5.5a.75.75 0 0 0 0-1.5A2.25 2.25 0 0 1 3.25 8.5a.75.75 0 0 0-.744-.75 5.49 5.49 0 0 0 2.003 4.5m8.241-2h.27A5.5 5.5 0 0 0 13.5 8c0-1.665-.74-3.158-1.91-4.166A2.25 2.25 0 0 1 9.5 5.25a.75.75 0 1 0 0 1.5A2.25 2.25 0 0 1 11.75 9v.25a1 1 0 0 0 1 1M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14" fill="currentColor" fill-rule="evenodd" />
        </svg>
        Travel
      </Tag>
      <Tag value="gaming">
        <svg aria-hidden="true" fill="none" viewBox="0 0 16 16">
          <g clip-path="url(#tag-prefix-rocket)">
            <path clip-rule="evenodd" d="m6.993 5.628-.787-.157-1.118-.224a1.13 1.13 0 0 0-1.024.31L2.837 6.785a1.15 1.15 0 0 0-.285.474l1.02.185a6.19 6.19 0 0 1 4.985 4.985l.185 1.02c.178-.055.34-.152.474-.285l1.227-1.227c.268-.268.384-.652.31-1.024l-.224-1.118-.157-.787.567-.568 1.243-1.242A4.5 4.5 0 0 0 13.5 4.015V2.5h-1.515a4.5 4.5 0 0 0-3.182 1.318L7.561 5.061zM12 9.5l1.243-1.243A6 6 0 0 0 15 4.015V2.5A1.5 1.5 0 0 0 13.5 1h-1.515a6 6 0 0 0-4.242 1.757L6.5 4l-1.118-.224a2.63 2.63 0 0 0-2.379.72L1.777 5.724A2.65 2.65 0 0 0 1 7.598c0 .522.373.97.887 1.063l1.417.258a4.69 4.69 0 0 1 3.777 3.777l.258 1.417c.093.514.54.887 1.063.887.703 0 1.377-.28 1.875-.777l1.226-1.226a2.63 2.63 0 0 0 .72-2.38zm-8.06 2.571c-.311-.31-.76-.28-1.005-.036-.233.233-.423.658-.527 1.247a5 5 0 0 0-.05.366q.184-.019.377-.053c.596-.106 1.017-.296 1.24-.52.245-.244.275-.693-.036-1.004M5 11.011c-.873-.874-2.273-.89-3.126-.036C.777 12.07.802 14.094.837 14.712c.007.12.06.23.145.315a.5.5 0 0 0 .32.145c.622.032 2.652.046 3.734-1.035.853-.854.837-2.253-.036-3.126m6.78-5.73a.75.75 0 0 0-1.06-1.061l-1.5 1.5a.75.75 0 0 0 1.06 1.06z" fill="currentColor" fill-rule="evenodd" />
          </g>
          <defs>
            <clipPath id="tag-prefix-rocket">
              <path d="M0 0h16v16H0z" fill="currentColor" />
            </clipPath>
          </defs>
        </svg>
        Gaming
      </Tag>
      <Tag value="shopping">
        <svg aria-hidden="true" fill="none" viewBox="0 0 16 16">
          <path clip-rule="evenodd" d="M5.174 3h5.652a1.5 1.5 0 0 1 1.49 1.328l.808 7A1.5 1.5 0 0 1 11.634 13H4.366a1.5 1.5 0 0 1-1.49-1.672l.808-7A1.5 1.5 0 0 1 5.174 3m-2.98 1.156A3 3 0 0 1 5.174 1.5h5.652a3 3 0 0 1 2.98 2.656l.808 7a3 3 0 0 1-2.98 3.344H4.366a3 3 0 0 1-2.98-3.344zM5 5.25a.75.75 0 0 1 1.5 0v.25a1.5 1.5 0 1 0 3 0v-.25a.75.75 0 0 1 1.5 0v.25a3 3 0 0 1-6 0z" fill="currentColor" fill-rule="evenodd" />
        </svg>
        Shopping
      </Tag>
      <Description>Tags with icons</Description>
    </TagGroup>

    <TagGroup label="With Avatars" selection-mode="single">
      <Tag value="fred">
        <Avatar class="demo-tag-group-avatar" size="sm">
          <AvatarImage src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg" alt="Fred" />
          <AvatarFallback>F</AvatarFallback>
        </Avatar>
        Fred
      </Tag>
      <Tag value="michael">
        <Avatar class="demo-tag-group-avatar" size="sm">
          <AvatarImage src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg" alt="Michael" />
          <AvatarFallback>M</AvatarFallback>
        </Avatar>
        Michael
      </Tag>
      <Tag value="jane">
        <Avatar class="demo-tag-group-avatar" size="sm">
          <AvatarImage src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg" alt="Jane" />
          <AvatarFallback>J</AvatarFallback>
        </Avatar>
        Jane
      </Tag>
      <Description>Tags with avatars</Description>
    </TagGroup>
  </div>
</template>

<script setup lang="ts">
import { Avatar, AvatarFallback, AvatarImage, Description, Tag, TagGroup } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-tag-group-prefix {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2rem;
}

.demo-tag-group-avatar {
  width: 1rem;
  height: 1rem;

  [data-slot="avatar-fallback"] {
    font-size: 0.625rem;
  }
}
</style>

With Remove Button

Removable tags
News Travel Gaming Shopping

<template>
  <TagGroup
    v-model:selected-keys="selectedKeys"
    label="Removable tags"
    selection-mode="multiple"
    @remove="handleRemove"
  >
    <Tag v-for="tag in tags" :key="tag.id" :value="tag.id">
      {{ tag.label }}
      <TagRemoveButton />
    </Tag>
  </TagGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Tag, TagGroup, TagRemoveButton } from '@heroui-vue/vue'

const tags = ref([
  { id: 'news', label: 'News' },
  { id: 'travel', label: 'Travel' },
  { id: 'gaming', label: 'Gaming' },
  { id: 'shopping', label: 'Shopping' },
])
const selectedKeys = ref<Array<string | number>>(tags.value.map((tag) => tag.id))

const handleRemove = (key: string | number) => {
  tags.value = tags.value.filter((tag) => tag.id !== key)
}
</script>

With List Data

Team members
FredF FredMichaelM MichaelJaneJ JaneAliceA AliceBobB BobCharlieC Charlie
Select team members for your project

Selected:

FredFFred
MichaelMMichael

<template>
  <div class="demo-tag-group-list-data">
    <TagGroup v-model:selected-keys="selectedKeys" label="Team members" selection-mode="multiple">
      <Tag v-for="member in members" :key="member.id" :value="member.id" variant="surface">
        <Avatar class="demo-tag-group-avatar" size="sm">
          <AvatarImage :src="member.avatar" :alt="member.name" />
          <AvatarFallback>{{ member.fallback }}</AvatarFallback>
        </Avatar>
        {{ member.name }}
      </Tag>
    </TagGroup>
    <Description>Select team members for your project</Description>

    <div v-if="selectedMembers.length" class="demo-tag-group-selected">
      <p>Selected:</p>
      <div class="demo-tag-group-selected__list">
        <div v-for="member in selectedMembers" :key="`${member.id}-selected`" class="demo-tag-group-selected__item">
          <Avatar class="demo-tag-group-avatar" size="sm">
            <AvatarImage :src="member.avatar" :alt="member.name" />
            <AvatarFallback>{{ member.fallback }}</AvatarFallback>
          </Avatar>
          <span>{{ member.name }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { Avatar, AvatarFallback, AvatarImage, Description, Tag, TagGroup } from '@heroui-vue/vue'

const members = [
  { id: 'fred', name: 'Fred', avatar: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', fallback: 'F' },
  { id: 'michael', name: 'Michael', avatar: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', fallback: 'M' },
  { id: 'jane', name: 'Jane', avatar: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', fallback: 'J' },
  { id: 'alice', name: 'Alice', avatar: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg', fallback: 'A' },
  { id: 'bob', name: 'Bob', avatar: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg', fallback: 'B' },
  { id: 'charlie', name: 'Charlie', avatar: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/black.jpg', fallback: 'C' },
]
const selectedKeys = ref<Array<string | number>>(['fred', 'michael'])
const selectedMembers = computed(() => members.filter((member) => selectedKeys.value.includes(member.id)))
</script>

<style lang="less">
.demo-tag-group-list-data {
  width: 24rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}

.demo-tag-group-avatar {
  width: 1rem;
  height: 1rem;

  [data-slot="avatar-fallback"] {
    font-size: 0.625rem;
  }
}

.demo-tag-group-selected {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.5rem;

  p {
    margin: 0;
    color: var(--color-muted);
    font-size: 0.875rem;
    font-weight: 500;
  }
}

.demo-tag-group-selected__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.demo-tag-group-selected__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border-radius: 0.5rem;
  background: var(--color-surface-tertiary);
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
}
</style>

Custom Render Function

Status
Online Away Busy Offline

<template>
  <TagGroup
    v-model:selected-keys="selectedKeys"
    class="demo-tag-group-custom"
    label="Status"
    selection-mode="single"
    variant="surface"
  >
    <Tag v-for="status in statuses" :key="status.id" :value="status.id">
      <span class="demo-tag-group-custom__dot" :style="{ backgroundColor: status.color }" />
      {{ status.label }}
    </Tag>
  </TagGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Tag, TagGroup } from '@heroui-vue/vue'

const selectedKeys = ref<Array<string | number>>(['online'])
const statuses = [
  { id: 'online', label: 'Online', color: '#17c964' },
  { id: 'away', label: 'Away', color: '#f5a524' },
  { id: 'busy', label: 'Busy', color: '#f31260' },
  { id: 'offline', label: 'Offline', color: '#71717a' },
]
</script>

<style lang="less">
.demo-tag-group-custom {
  align-items: flex-start;
}

.demo-tag-group-custom__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
}
</style>

Styling

TagGroup owns the label, list layout, selection state, disabled keys, and remove callbacks. Use component props for native tag behavior first. Demo-only layout styles should stay in each demo file and be shown in the source panel.

API

PropTypeDefaultDescription
selectionMode'none' | 'single' | 'multiple''none'Enables single or multiple tag selection
selectedKeysArray<string | number>undefinedControlled selected keys
defaultSelectedKeysArray<string | number>[]Initial uncontrolled selected keys
disabledKeysArray<string | number>[]Keys that cannot be selected
disabled / isDisabledbooleanfalseDisables the whole group
isInvalidbooleanfalseMarks the group and child tags invalid
size'sm' | 'md' | 'lg''md'Size inherited by child tags
variant'default' | 'surface''default'Visual style inherited by child tags
EventPayloadDescription
update:selectedKeysArray<string | number>Emitted when selection changes
selection-changeArray<string | number>Emitted when selection changes
removestring | numberEmitted when a tag remove button removes a tag key

Released under the Apache-2.0 License.