Skip to content

TagGroup ​

A tag group is a focusable set of labels. Each Tag can be selected, removed, both or neither — and with none of those turned on it is still a group of tags rather than a run of styled spans, because the keyboard can walk it and a screen reader can count it.

ts
import { Tag, TagGroup, TagGroupList } from "ropav";

TagGroup owns the state and the field wiring; TagGroupList is the collection the tags sit in. They are separate so a Label, a Description or an ErrorMessage can be a sibling of the list rather than a child of it, and still be associated with it by id.

Categories
News
Travel
Gaming
variant
size
selectionMode
template
<TagGroup>
  <Label>Categories</Label>
  <TagGroupList>
    <Tag id="news">News</Tag>
    <Tag id="travel">Travel</Tag>
    <Tag id="gaming">Gaming</Tag>
  </TagGroupList>
</TagGroup>

selection-mode is none until you say otherwise. size and variant set on the group are shared by every tag in it, and disabled-keys takes individual tags out.

Removing ​

Supplying on-remove is what puts a remove button on each tag — the presence of the handler is the signal, so there is no second prop to keep in step with it. It is called with a Set of keys, which is what lets removing a selected tag take the rest of the selection with it.

Categories
News
Travel
Gaming
Shopping
Backspace removes the focused tag.
tag-group-removable.vue
vue
<script setup lang="ts">
import { Description, EmptyState, Label, Tag, TagGroup, TagGroupList } from "ropav";
import { shallowRef } from "vue";

const tags = shallowRef([
  { id: "news", name: "News" },
  { id: "travel", name: "Travel" },
  { id: "gaming", name: "Gaming" },
  { id: "shopping", name: "Shopping" },
]);

const onRemove = (keys: Set<string | number>) => {
  tags.value = tags.value.filter((tag) => !keys.has(tag.id));
};
</script>

<template>
  <TagGroup class="group" :on-remove="onRemove" selection-mode="multiple">
    <Label>Categories</Label>
    <TagGroupList>
      <Tag v-for="tag in tags" :id="tag.id" :key="tag.id" :text-value="tag.name">
        {{ tag.name }}
      </Tag>
      <template #empty>
        <EmptyState class="empty">Every category removed</EmptyState>
      </template>
    </TagGroupList>
    <Description>Backspace removes the focused tag.</Description>
  </TagGroup>
</template>

<style scoped>
.group {
  width: 100%;
  max-width: var(--rp-container-sm);
}

.empty {
  padding: var(--rp-spacing);
}
</style>

The list's #empty slot is what shows once the last tag has gone. A tag whose content is more than a string needs a text-value, which is what typeahead matches on and what the tag is announced by.

Accessibility ​

  • The list is a grid and each tag a row with a single cell, which is the pattern that lets a tag hold a remove button without that button becoming a second tab stop.
  • The whole group is one tab stop. Arrow keys move between tags, typing jumps to a match, and Backspace or Delete removes the focused tag when removing is on.
  • With no visible Label, give TagGroupList an aria-label.
  • An empty list reports role="group" rather than grid, so an empty state is not announced as a table with nothing in it.

API ​

TagGroup

PropTypeDefaultDescription
defaultSelectedKeys"all" | Iterable<CollectionKey>—
disabledBehavior"all" | "selection""all"
disabledKeysIterable<CollectionKey>—
disallowEmptySelectionboolean—
onRemove((keys: Set<CollectionKey>) => void)—Called with the keys to remove. Supplying it is what offers a remove button on each tag — the same signal React reads.
selectedKeys"all" | Iterable<CollectionKey>—
selectionBehavior"replace" | "toggle""toggle"What a press means when several tags can be selected.
selectionMode"none" | "single" | "multiple""none"Whether one, many or no tags can be selected.
size"xs" | "sm" | "md" | "lg"—Size shared by every tag in the group.
variant"default" | "surface"—Visual variant shared by every tag in the group.

Slots: default.

EventPayload
selectionChangeCollectionSelection
update:selectedKeysCollectionSelection

TagGroupList

PropTypeDefaultDescription
ariaLabelstring—Accessible name for the list, for when the group has no visible label.

Slots: default, empty.

Tag and its remove button come from the same import, and are documented here because a group is the only place they appear.

Tag

PropTypeDefaultDescription
idCollectionKey—The tag's key within the group. Generated when omitted.
isDisabledboolean—Disables the tag: it can be neither focused nor selected.
textValuestring—Text the tag is matched on and announced by. Read from its content when omitted.

The default slot receives:

Slot propType
isDisabledboolean
isFocusedboolean
isFocusVisibleboolean
isHoveredboolean
isPressedboolean
isSelectedboolean

Slots: remove.

TagRemoveButton

Slots: default.