# TagGroup **Category**: react **URL**: https://www.heroui.com/docs/react/components/tag-group **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/react/components/(collections)/tag-group.mdx > A focusable list of tags with support for keyboard navigation, selection, and removal *** ## Import ```tsx import { TagGroup } from '@heroui/react'; ``` ### Usage ```tsx "use client"; import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons"; import {Tag, TagGroup} from "@heroui/react"; export function TagGroupBasic() { return ( News Travel Gaming Shopping ); } ``` ### Anatomy ```tsx import { TagGroup, Tag, Label, Description, ErrorMessage } from '@heroui/react'; export default () => ( ) ``` ### Sizes ```tsx "use client"; import {Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupSizes() { return (
News Travel Gaming News Travel Gaming News Travel Gaming
); } ``` ### Variants ```tsx "use client"; import {Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupVariants() { return (
News Travel Gaming News Travel Gaming
); } ``` ### Disabled ```tsx "use client"; import {Description, Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupDisabled() { return (
News Travel Gaming Some tags are disabled News Travel Gaming Tags disabled via disabledKeys prop
); } ``` ### Selection Modes ```tsx "use client"; import type {Key} from "@heroui/react"; import {Description, Label, Tag, TagGroup} from "@heroui/react"; import {useState} from "react"; export function TagGroupSelectionModes() { const [singleSelected, setSingleSelected] = useState>(new Set(["news"])); const [multipleSelected, setMultipleSelected] = useState>( new Set(["news", "travel"]), ); return (
setSingleSelected(keys)} > News Travel Gaming Shopping Choose one category setMultipleSelected(keys)} > News Travel Gaming Shopping Choose multiple categories
); } ``` ### Controlled ```tsx "use client"; import type {Key} from "@heroui/react"; import {Description, Label, Tag, TagGroup} from "@heroui/react"; import {useState} from "react"; export function TagGroupControlled() { const [selected, setSelected] = useState>(new Set(["news", "travel"])); return (
setSelected(keys)} > News Travel Gaming Shopping Selected: {Array.from(selected).length > 0 ? Array.from(selected).join(", ") : "None"}
); } ``` ### With Error Message ```tsx "use client"; import type {Key} from "@heroui/react"; import {Description, ErrorMessage, Label, Tag, TagGroup} from "@heroui/react"; import {useMemo, useState} from "react"; export function TagGroupWithErrorMessage() { const [selected, setSelected] = useState>(new Set()); const isInvalid = useMemo(() => Array.from(selected).length === 0, [selected]); return ( setSelected(keys)} > Laundry Fitness center Parking Swimming pool Breakfast {isInvalid ? "Select at least one category" : "Selected: " + Array.from(selected).join(", ")} {!!isInvalid && <>Please select at least one category} ); } ``` ### With Prefix ```tsx "use client"; import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons"; import {Avatar, Description, Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupWithPrefix() { return (
News Travel Gaming Shopping Tags with icons F Fred M Michael J Jane Tags with avatars
); } ``` ### With Remove Button ```tsx "use client"; import type {Key} from "@heroui/react"; import {CircleXmarkFill} from "@gravity-ui/icons"; import {Description, EmptyState, Label, Tag, TagGroup} from "@heroui/react"; import {useState} from "react"; export function TagGroupWithRemoveButton() { type TagItem = {id: string; name: string}; const [tags, setTags] = useState([ {id: "news", name: "News"}, {id: "travel", name: "Travel"}, {id: "gaming", name: "Gaming"}, {id: "shopping", name: "Shopping"}, ]); const [frameworks, setFrameworks] = useState([ {id: "react", name: "React"}, {id: "vue", name: "Vue"}, {id: "angular", name: "Angular"}, {id: "svelte", name: "Svelte"}, ]); const onRemoveTags = (keys: Set) => { setTags(tags.filter((tag) => !keys.has(tag.id))); }; const onRemoveFrameworks = (keys: Set) => { setFrameworks(frameworks.filter((framework) => !keys.has(framework.id))); }; return (
No categories found} > {(tag) => ( {tag.name} )} Click the X to remove tags
No frameworks found} > {(tag) => ( {(renderProps) => ( <> {tag.name} {!!renderProps.allowsRemoving && ( )} )} )} Custom remove button with icon
); } ``` ### With List Data ```tsx "use client"; import type {Key} from "@heroui/react"; import {Avatar, Description, EmptyState, Label, Tag, TagGroup, useListData} from "@heroui/react"; export function TagGroupWithListData() { type User = { id: string; name: string; avatar: string; fallback: string; }; const list = useListData({ getKey: (item) => item.id, initialItems: [ { avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg", fallback: "F", id: "fred", name: "Fred", }, { avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg", fallback: "M", id: "michael", name: "Michael", }, { avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg", fallback: "J", id: "jane", name: "Jane", }, { avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg", fallback: "A", id: "alice", name: "Alice", }, { avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg", fallback: "B", id: "bob", name: "Bob", }, { avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/black.jpg", fallback: "C", id: "charlie", name: "Charlie", }, ], initialSelectedKeys: new Set(["fred", "michael"]), }); const onRemove = (keys: Set) => { list.remove(...keys); }; return (
list.setSelectedKeys(keys)} > No team members} > {(user) => ( {user.fallback} {user.name} )} Select team members for your project {list.selectedKeys !== "all" && Array.from(list.selectedKeys).length > 0 && (

Selected:

{Array.from(list.selectedKeys).map((key) => { const user = list.getItem(key); if (!user) return null; return (
{user.fallback} {user.name}
); })}
)}
); } ``` ### Custom Render Function ```tsx "use client"; import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons"; import {Tag, TagGroup} from "@heroui/react"; export function CustomRenderFunction() { return (
} selectionMode="single" > News Travel Gaming Shopping ); } ``` ## Related Components - **Label**: Accessible label for form controls - **Description**: Helper text for form fields - **ErrorMessage**: Displays validation error messages for components with validation support ## Styling ### Passing Tailwind CSS classes ```tsx import { TagGroup, Tag, Label } from '@heroui/react'; function CustomTagGroup() { return ( Custom Styled ); } ``` ### Customizing the component classes To customize the TagGroup component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .tag-group { @apply flex flex-col gap-2; } .tag-group__list { @apply flex flex-wrap gap-2; } .tag { @apply rounded-full px-3 py-1; } .tag__remove-button { @apply ml-1; } } ``` HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes The TagGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tag-group.css) and [tag.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tag.css)): #### Base Classes - `.tag-group` - Base tag group container - `.tag-group__list` - Container for the list of tags - `.tag` - Base tag styles - `.tag__remove-button` - Remove button trigger #### Slot Classes - `.tag-group [slot="description"]` - Description slot styles - `.tag-group [slot="errorMessage"]` - ErrorMessage slot styles #### Size Classes - `.tag--sm` - Small size tag - `.tag--md` - Medium size tag (default) - `.tag--lg` - Large size tag #### Variant Classes - `.tag--default` - Default variant - `.tag--surface` - Surface variant with surface background #### State Classes - `.tag[data-selected="true"]` - Selected tag state - `.tag[data-disabled="true"]` - Disabled tag state - `.tag[data-hovered="true"]` - Hovered tag state - `.tag[data-pressed="true"]` - Pressed tag state - `.tag[data-focus-visible="true"]` - Focused tag state (keyboard focus) ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: - **Hover**: `:hover` or `[data-hovered="true"]` on tag - **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on tag - **Pressed**: `:active` or `[data-pressed="true"]` on tag - **Selected**: `[data-selected="true"]` or `[aria-selected="true"]` on tag - **Disabled**: `:disabled` or `[data-disabled="true"]` on tag ## API Reference ### TagGroup Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | The type of selection that is allowed | | `selectedKeys` | `Selection` | - | The currently selected keys (controlled) | | `defaultSelectedKeys` | `Selection` | - | The initial selected keys (uncontrolled) | | `onSelectionChange` | `(keys: Selection) => void` | - | Handler called when the selection changes | | `disabledKeys` | `Iterable` | - | Keys of disabled tags | | `isDisabled` | `boolean` | - | Whether the tag group is disabled | | `onRemove` | `(keys: Set) => void` | - | Handler called when tags are removed | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the tags in the group | | `variant` | `"default" \| "surface"` | `"default"` | Visual variant of the tags | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | TagGroup content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function.| ### TagGroup.List Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `items` | `Iterable` | - | The items to display in the tag list | | `renderEmptyState` | `() => ReactNode` | - | Function to render when the list is empty | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | TagList content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function.| ### Tag Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `id` | `Key` | - | The unique identifier for the tag | | `textValue` | `string` | - | A string representation of the tag's content, used for accessibility | | `isDisabled` | `boolean` | - | Whether the tag is disabled | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Tag content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function.| **Note**: `size`, `variant` are inherited from the parent `TagGroup` component and cannot be set directly on individual `Tag` components. ### Tag.RemoveButton Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Custom remove button content (defaults to close icon) | **Note**: The `Tag.RemoveButton` component supports customization similar to `SearchField.ClearButton`. When `onRemove` is provided to `TagGroup`: - **Auto-rendering**: If no custom `Tag.RemoveButton` is included in the `Tag` children, a default remove button is automatically rendered. - **Custom button**: If a custom `Tag.RemoveButton` is provided as a child of `Tag`, it will be used instead of the auto-rendered button. - **Custom icon**: You can pass custom content (like icons) to `Tag.RemoveButton` children to customize the appearance. **Example - Auto-rendered (default)**: ```tsx News {/* Remove button is automatically rendered */} ``` **Example - Custom RemoveButton with icon**: ```tsx News ``` **Example - Custom RemoveButton in render props**: ```tsx {(renderProps) => ( <> News {!!renderProps.allowsRemoving && ( )} )} ``` ### RenderProps When using render functions with TagGroup.List, these values are provided: | Prop | Type | Description | |------|------|-------------| | `isSelected` | `boolean` | Whether the tag is selected | | `isDisabled` | `boolean` | Whether the tag is disabled | | `isHovered` | `boolean` | Whether the tag is hovered | | `isPressed` | `boolean` | Whether the tag is pressed | | `isFocused` | `boolean` | Whether the tag is focused | | `isFocusVisible` | `boolean` | Whether the tag has keyboard focus |