Skip to content

Navigation

Tag Group

A row of removable, selectable little labels.

When to use it: Use it for filters, keywords, and picked options.

On this page

Example

news
sports
arts
travel
Tag Group.tsxtsx
import { useState } from "react";
import { Button, Label, Tag, TagGroup, TagList } from "@comp0/react";
import { XMarkIcon } from "@heroicons/react/16/solid";

export function Example() {
  const [tags, setTags] = useState(["news", "sports", "arts", "travel"]);
  const [selected, setSelected] = useState<string[]>(["news"]);
  const remove = (value: string) => {
    setTags((current) => current.filter((entry) => entry !== value));
    setSelected((current) => current.filter((entry) => entry !== value));
  };

  return (
    <TagGroup value={selected} onChange={setSelected} onRemove={remove}>
      <Label>Topics</Label>
      <TagList className="flex max-w-sm flex-wrap gap-2">
        {tags.map((tag) => (
          <Tag
            key={tag}
            value={tag}
            className="group cursor-pointer rounded-full bg-zinc-100 outline-teal-600 focus-visible:outline-2 data-selected:bg-teal-100 dark:bg-zinc-800 dark:outline-teal-400 dark:data-selected:bg-teal-950 [&>[role=gridcell]]:flex [&>[role=gridcell]]:items-center [&>[role=gridcell]]:gap-1.5 [&>[role=gridcell]]:px-3 [&>[role=gridcell]]:py-1.5"
          >
            <span className="text-base capitalize sm:text-sm">{tag}</span>
            <Button
              aria-label={`Remove ${tag}`}
              className="rounded-full p-0.5 text-zinc-500 hover:bg-zinc-950/10 hover:text-zinc-800 dark:text-zinc-400 dark:hover:bg-white/10 dark:hover:text-zinc-100"
              onClick={() => remove(tag)}
            >
              <XMarkIcon className="size-3.5" aria-hidden="true" />
            </Button>
          </Tag>
        ))}
      </TagList>
    </TagGroup>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. TagGroup

    Selection and removal provider. Does not add a DOM element.

  2. Label

    Visible name connected to TagList. Owns a DOM element.

  3. TagList

    Grid container and the tags' keyboard boundary. Owns a DOM element.

  4. Tag

    Removable, selectable label that can hold a control. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Start TagGroup with selection and removal callbacks.

  2. 2

    Add the supporting parts

    Add a visible Label, then put the tags inside TagList.

  3. 3

    Make the behavior clear

    Give every Tag a value; wire onRemove for Delete and value/onChange for selection.

    Exampletsx
    <TagGroup onRemove={remove}><Label>Filters</Label><TagList><Tag value="news">News</Tag></TagList></TagGroup>

Keyboard

Moves to the next tag.
Moves to the previous tag.
Home
Moves to the first tag.
End
Moves to the last tag.
Space
Toggles the focused tag's selection.
Toggles the focused tag's selection.

Forms and accessibility

Selection does not create a native form value; mirror it into hidden inputs when a form needs it.

Accessibility checklist

  • Use a visible Label for the tag list, or give TagList an aria-label when no label is shown.
  • Keep remove controls reachable by pointer; Delete removes from the keyboard.
  • Show selection with more than color.

API reference

Importtsx
import { Button, Label, Tag, TagGroup, TagList } from "@comp0/react";

TagGroup

Context only

Selection and removal provider.

PropTypeDescription
valuestring[]Controlled selected tags.
defaultValuestring[]Initial selected tags.
onChange(value: string[]) => voidReceives the next selected tags.
onRemove(value: string) => voidReceives a tag removed with Delete or Backspace.

Label

DOM element

Visible name connected to TagList.

TagList

DOM element

Grid container and the tags' keyboard boundary.

PropTypeDescription
aria-labelstringNames the tag list when no visible Label is shown.

Tag

DOM element

Removable, selectable label that can hold a control.

PropTypeDescription
valuestringThis tag’s identity.
disabledbooleanDisables the tag.
textValuestringOverrides the text crawled from children when markup makes it ambiguous.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

[data-selected]

on Tag

The tag is selected.
[data-disabled]

on Tag

The tag is disabled.
:focus-visible

on Tag

The tag has keyboard focus.

Keep exploring