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

Loading example…
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.
Del
Removes the focused tag when onRemove is wired.
⌫
Removes the focused tag when onRemove is wired.

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.

Tag

Style hookMeaning
[data-selected]The tag is selected.
[data-disabled]The tag is disabled.
:focus-visibleThe tag has keyboard focus.

Keep exploring