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.
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.
<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.
tag-group-removable.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
gridand 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, giveTagGroupListanaria-label. - An empty list reports
role="group"rather thangrid, so an empty state is not announced as a table with nothing in it.
API
TagGroup
| Prop | Type | Default | Description |
|---|---|---|---|
defaultSelectedKeys | "all" | Iterable<CollectionKey> | — | |
disabledBehavior | "all" | "selection" | "all" | |
disabledKeys | Iterable<CollectionKey> | — | |
disallowEmptySelection | boolean | — | |
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.
| Event | Payload |
|---|---|
selectionChange | CollectionSelection |
update:selectedKeys | CollectionSelection |
TagGroupList
| Prop | Type | Default | Description |
|---|---|---|---|
ariaLabel | string | — | 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
| Prop | Type | Default | Description |
|---|---|---|---|
id | CollectionKey | — | The tag's key within the group. Generated when omitted. |
isDisabled | boolean | — | Disables the tag: it can be neither focused nor selected. |
textValue | string | — | Text the tag is matched on and announced by. Read from its content when omitted. |
The default slot receives:
| Slot prop | Type |
|---|---|
isDisabled | boolean |
isFocused | boolean |
isFocusVisible | boolean |
isHovered | boolean |
isPressed | boolean |
isSelected | boolean |
Slots: remove.
TagRemoveButton
Slots: default.