TagGroup

A tag group is a focusable list of labels, categories, keywords, filters, or other items, with support for keyboard navigation, selection, and removal.

installyarn add react-aria-components
version1.1.1
usageimport {TagGroup, TagList, Tag} from 'react-aria-components'

Example#


import {TagGroup, TagList, Tag, Label} from 'react-aria-components';

<TagGroup selectionMode="multiple">
  <Label>Categories</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
</TagGroup>
import {
  Label,
  Tag,
  TagGroup,
  TagList
} from 'react-aria-components';

<TagGroup selectionMode="multiple">
  <Label>Categories</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
</TagGroup>
import {
  Label,
  Tag,
  TagGroup,
  TagList
} from 'react-aria-components';

<TagGroup selectionMode="multiple">
  <Label>
    Categories
  </Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
</TagGroup>
Show CSS
@import "@react-aria/example-theme";

.react-aria-TagGroup {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: small;
  color: var(--text-color);
}

.react-aria-TagList {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.react-aria-Tag {
  color: var(--text-color);
  border: 1px solid var(--border-color);
  forced-color-adjust: none;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 0.929rem;
  outline: none;
  cursor: default;
  display: flex;
  align-items: center;
  transition: border-color 200ms;

  &[data-hovered] {
    border-color: var(--border-color-hover);
  }

  &[data-focus-visible] {
    outline: 2px solid var(--focus-ring-color);
    outline-offset: 2px;
  }

  &[data-selected] {
    border-color: var(--highlight-background);
    background: var(--highlight-background);
    color: var(--highlight-foreground);
  }
}
@import "@react-aria/example-theme";

.react-aria-TagGroup {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: small;
  color: var(--text-color);
}

.react-aria-TagList {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.react-aria-Tag {
  color: var(--text-color);
  border: 1px solid var(--border-color);
  forced-color-adjust: none;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 0.929rem;
  outline: none;
  cursor: default;
  display: flex;
  align-items: center;
  transition: border-color 200ms;

  &[data-hovered] {
    border-color: var(--border-color-hover);
  }

  &[data-focus-visible] {
    outline: 2px solid var(--focus-ring-color);
    outline-offset: 2px;
  }

  &[data-selected] {
    border-color: var(--highlight-background);
    background: var(--highlight-background);
    color: var(--highlight-foreground);
  }
}
@import "@react-aria/example-theme";

.react-aria-TagGroup {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: small;
  color: var(--text-color);
}

.react-aria-TagList {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.react-aria-Tag {
  color: var(--text-color);
  border: 1px solid var(--border-color);
  forced-color-adjust: none;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 0.929rem;
  outline: none;
  cursor: default;
  display: flex;
  align-items: center;
  transition: border-color 200ms;

  &[data-hovered] {
    border-color: var(--border-color-hover);
  }

  &[data-focus-visible] {
    outline: 2px solid var(--focus-ring-color);
    outline-offset: 2px;
  }

  &[data-selected] {
    border-color: var(--highlight-background);
    background: var(--highlight-background);
    color: var(--highlight-foreground);
  }
}

Features#


A static tag list can be built using <ul> or <ol> HTML elements, but does not support any user interactions. HTML lists are meant for static content, rather than lists with rich interactions such as keyboard navigation, item selection, removal, etc. TagGroup helps achieve accessible and interactive tag list components that can be styled as needed.

  • Keyboard navigation – Tags can be navigated using the arrow keys, along with page up/down, home/end, etc.
  • Removable – Tags can be removed from the tag group by clicking a remove button or pressing the backspace key.
  • Item selection – Single or multiple selection, with support for disabled items and both toggle and replace selection behaviors.
  • Accessible – Follows the ARIA grid pattern, with additional selection announcements via an ARIA live region. Extensively tested across many devices and assistive technologies to ensure announcements and behaviors are consistent.
  • Styleable – Items include builtin states for styling, such as hover, press, focus, selected, and disabled.

Anatomy#


TravelingGrid rowGrid cellTag labelHikingRemove buttonTag groupCategoriesGroup label

A tag group consists of label and a list of tags. Each tag should include a visual label, and may optionally include a remove button. If a visual label is not included in a tag group, then an aria-label or aria-labelledby prop must be passed to identify it to assistive technology.

TagGroup also supports optional description and error message slots, which can be used to provide more context about the tag group, and any validation messages. These are linked with the tag group via the aria-describedby attribute.

import {Button, Label, Tag, TagGroup, TagList, Text} from 'react-aria-components';

<TagGroup>
  <Label />
  <TagList>
    <Tag>
      <Button slot="remove" />
    </Tag>
  </TagList>
  <Text slot="description" />
  <Text slot="errorMessage" />
</TagGroup>
import {
  Button,
  Label,
  Tag,
  TagGroup,
  TagList,
  Text
} from 'react-aria-components';

<TagGroup>
  <Label />
  <TagList>
    <Tag>
      <Button slot="remove" />
    </Tag>
  </TagList>
  <Text slot="description" />
  <Text slot="errorMessage" />
</TagGroup>
import {
  Button,
  Label,
  Tag,
  TagGroup,
  TagList,
  Text
} from 'react-aria-components';

<TagGroup>
  <Label />
  <TagList>
    <Tag>
      <Button slot="remove" />
    </Tag>
  </TagList>
  <Text slot="description" />
  <Text slot="errorMessage" />
</TagGroup>

Concepts#

TagGroup makes use of the following concepts:

Collections
Defining collections of items, async loading, and updating items over time.
Selection
Interactions and data structures to represent selection.

Composed components#

A TagGroup uses the following components, which may also be used standalone or reused in other components.

Label
A label provides context for an element.
Button
A button allows a user to perform an action.

Starter kits#


To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.

Vanilla CSS
Download ZIP
Preview
Tailwind CSS
Download ZIP
Preview

Reusable wrappers#


If you will use a TagGroup in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.

This example wraps TagGroup and all of its children together into a single component which accepts a label prop and children, which are passed through to the right places. It also shows how to use the description and errorMessage slots to render help text (see below for details). The Tag component is also wrapped to automatically render a remove button when the onRemove prop is provided to the TagGroup.

import type {TagGroupProps, TagListProps, TagProps} from 'react-aria-components';
import {Button, Text} from 'react-aria-components';

interface MyTagGroupProps<T>
  extends
    Omit<TagGroupProps, 'children'>,
    Pick<TagListProps<T>, 'items' | 'children' | 'renderEmptyState'> {
  label?: string;
  description?: string;
  errorMessage?: string;
}

function MyTagGroup<T extends object>(
  {
    label,
    description,
    errorMessage,
    items,
    children,
    renderEmptyState,
    ...props
  }: MyTagGroupProps<T>
) {
  return (
    <TagGroup {...props}>
      <Label>{label}</Label>
      <TagList items={items} renderEmptyState={renderEmptyState}>
        {children}
      </TagList>
      {description && <Text slot="description">{description}</Text>}
      {errorMessage && <Text slot="errorMessage">{errorMessage}</Text>}
    </TagGroup>
  );
}

function MyTag({ children, ...props }: TagProps) {
  let textValue = typeof children === 'string' ? children : undefined;
  return (
    <Tag textValue={textValue} {...props}>
      {({ allowsRemoving }) => (
        <>
          {children}
          {allowsRemoving && <Button slot="remove"></Button>}
        </>
      )}
    </Tag>
  );
}

<MyTagGroup label="Ice cream flavor" selectionMode="single">
  <MyTag>Chocolate</MyTag>
  <MyTag>Mint</MyTag>
  <MyTag>Strawberry</MyTag>
  <MyTag>Vanilla</MyTag>
</MyTagGroup>
import type {
  TagGroupProps,
  TagListProps,
  TagProps
} from 'react-aria-components';
import {Button, Text} from 'react-aria-components';

interface MyTagGroupProps<T>
  extends
    Omit<TagGroupProps, 'children'>,
    Pick<
      TagListProps<T>,
      'items' | 'children' | 'renderEmptyState'
    > {
  label?: string;
  description?: string;
  errorMessage?: string;
}

function MyTagGroup<T extends object>(
  {
    label,
    description,
    errorMessage,
    items,
    children,
    renderEmptyState,
    ...props
  }: MyTagGroupProps<T>
) {
  return (
    <TagGroup {...props}>
      <Label>{label}</Label>
      <TagList
        items={items}
        renderEmptyState={renderEmptyState}
      >
        {children}
      </TagList>
      {description && (
        <Text slot="description">{description}</Text>
      )}
      {errorMessage && (
        <Text slot="errorMessage">{errorMessage}</Text>
      )}
    </TagGroup>
  );
}

function MyTag({ children, ...props }: TagProps) {
  let textValue = typeof children === 'string'
    ? children
    : undefined;
  return (
    <Tag textValue={textValue} {...props}>
      {({ allowsRemoving }) => (
        <>
          {children}
          {allowsRemoving && (
            <Button slot="remove"></Button>
          )}
        </>
      )}
    </Tag>
  );
}

<MyTagGroup
  label="Ice cream flavor"
  selectionMode="single"
>
  <MyTag>Chocolate</MyTag>
  <MyTag>Mint</MyTag>
  <MyTag>Strawberry</MyTag>
  <MyTag>Vanilla</MyTag>
</MyTagGroup>
import type {
  TagGroupProps,
  TagListProps,
  TagProps
} from 'react-aria-components';
import {
  Button,
  Text
} from 'react-aria-components';

interface MyTagGroupProps<
  T
> extends
  Omit<
    TagGroupProps,
    'children'
  >,
  Pick<
    TagListProps<T>,
    | 'items'
    | 'children'
    | 'renderEmptyState'
  > {
  label?: string;
  description?: string;
  errorMessage?: string;
}

function MyTagGroup<
  T extends object
>(
  {
    label,
    description,
    errorMessage,
    items,
    children,
    renderEmptyState,
    ...props
  }: MyTagGroupProps<T>
) {
  return (
    <TagGroup {...props}>
      <Label>
        {label}
      </Label>
      <TagList
        items={items}
        renderEmptyState={renderEmptyState}
      >
        {children}
      </TagList>
      {description && (
        <Text slot="description">
          {description}
        </Text>
      )}
      {errorMessage && (
        <Text slot="errorMessage">
          {errorMessage}
        </Text>
      )}
    </TagGroup>
  );
}

function MyTag(
  { children, ...props }:
    TagProps
) {
  let textValue =
    typeof children ===
        'string'
      ? children
      : undefined;
  return (
    <Tag
      textValue={textValue}
      {...props}
    >
      {(
        {
          allowsRemoving
        }
      ) => (
        <>
          {children}
          {allowsRemoving &&
            (
              <Button slot="remove"></Button>
            )}
        </>
      )}
    </Tag>
  );
}

<MyTagGroup
  label="Ice cream flavor"
  selectionMode="single"
>
  <MyTag>
    Chocolate
  </MyTag>
  <MyTag>Mint</MyTag>
  <MyTag>
    Strawberry
  </MyTag>
  <MyTag>
    Vanilla
  </MyTag>
</MyTagGroup>

Removing tags#


The onRemove prop can be used to allow the user to remove tags. In the above example, an additional <Button slot="remove> element is rendered when a tag group allows removing. The user can also press the backspace key while a tag is focused to remove the tag from the group. Additionally, when selection is enabled, all selected items will be deleted when pressing the backspace key on a selected tag.

import {useListData} from 'react-stately';

function Example() {
  let list = useListData({
    initialItems: [
      { id: 1, name: 'News' },
      { id: 2, name: 'Travel' },
      { id: 3, name: 'Gaming' },
      { id: 4, name: 'Shopping' }
    ]
  });

  return (
    <MyTagGroup
      label="Categories"
      items={list.items}
      onRemove={(keys) => list.remove(...keys)}    >
      {(item) => <MyTag>{item.name}</MyTag>}
    </MyTagGroup>
  );
}
import {useListData} from 'react-stately';

function Example() {
  let list = useListData({
    initialItems: [
      { id: 1, name: 'News' },
      { id: 2, name: 'Travel' },
      { id: 3, name: 'Gaming' },
      { id: 4, name: 'Shopping' }
    ]
  });

  return (
    <MyTagGroup
      label="Categories"
      items={list.items}
      onRemove={(keys) => list.remove(...keys)}    >
      {(item) => <MyTag>{item.name}</MyTag>}
    </MyTagGroup>
  );
}
import {useListData} from 'react-stately';

function Example() {
  let list = useListData(
    {
      initialItems: [
        {
          id: 1,
          name: 'News'
        },
        {
          id: 2,
          name: 'Travel'
        },
        {
          id: 3,
          name: 'Gaming'
        },
        {
          id: 4,
          name:
            'Shopping'
        }
      ]
    }
  );

  return (
    <MyTagGroup
      label="Categories"
      items={list.items}
      onRemove={(keys) =>
        list.remove(
          ...keys
        )}    >
      {(item) => (
        <MyTag>
          {item.name}
        </MyTag>
      )}
    </MyTagGroup>
  );
}
Show CSS
.react-aria-Tag {
  [slot=remove] {
    background: none;
    border: none;
    padding: 0;
    margin-left: 8px;
    color: var(--text-color-base);
    transition: color 200ms;
    outline: none;
    font-size: 0.95em;

    &[data-hovered] {
      color: var(--text-color-hover);
    }
  }

  &[data-selected] {
    [slot=remove] {
      color: inherit;
    }
  }
}
.react-aria-Tag {
  [slot=remove] {
    background: none;
    border: none;
    padding: 0;
    margin-left: 8px;
    color: var(--text-color-base);
    transition: color 200ms;
    outline: none;
    font-size: 0.95em;

    &[data-hovered] {
      color: var(--text-color-hover);
    }
  }

  &[data-selected] {
    [slot=remove] {
      color: inherit;
    }
  }
}
.react-aria-Tag {
  [slot=remove] {
    background: none;
    border: none;
    padding: 0;
    margin-left: 8px;
    color: var(--text-color-base);
    transition: color 200ms;
    outline: none;
    font-size: 0.95em;

    &[data-hovered] {
      color: var(--text-color-hover);
    }
  }

  &[data-selected] {
    [slot=remove] {
      color: inherit;
    }
  }
}

Selection#


TagGroup supports multiple selection modes. By default, selection is disabled, however this can be changed using the selectionMode prop. Use defaultSelectedKeys to provide a default set of selected items (uncontrolled) and selectedKeys to set the selected items (controlled). The value of the selected keys must match the id prop of the items. See the Selection guide for more details.

import type {Selection} from 'react-aria-components';

function Example() {
  let [selected, setSelected] = React.useState<Selection>(new Set(['parking']));

  return (
    <>
      <MyTagGroup
        label="Amenities"
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}      >
        <MyTag id="laundry">Laundry</MyTag>
        <MyTag id="fitness">Fitness center</MyTag>
        <MyTag id="parking">Parking</MyTag>
        <MyTag id="pool">Swimming pool</MyTag>
        <MyTag id="breakfast">Breakfast</MyTag>
      </MyTagGroup>
      <p>
        Current selection (controlled):{' '}
        {selected === 'all' ? 'all' : [...selected].join(', ')}
      </p>
    </>
  );
}
import type {Selection} from 'react-aria-components';

function Example() {
  let [selected, setSelected] = React.useState<Selection>(
    new Set(['parking'])
  );

  return (
    <>
      <MyTagGroup
        label="Amenities"
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}      >
        <MyTag id="laundry">Laundry</MyTag>
        <MyTag id="fitness">Fitness center</MyTag>
        <MyTag id="parking">Parking</MyTag>
        <MyTag id="pool">Swimming pool</MyTag>
        <MyTag id="breakfast">Breakfast</MyTag>
      </MyTagGroup>
      <p>
        Current selection (controlled): {selected === 'all'
          ? 'all'
          : [...selected].join(', ')}
      </p>
    </>
  );
}
import type {Selection} from 'react-aria-components';

function Example() {
  let [
    selected,
    setSelected
  ] = React.useState<
    Selection
  >(
    new Set(['parking'])
  );

  return (
    <>
      <MyTagGroup
        label="Amenities"
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}      >
        <MyTag id="laundry">
          Laundry
        </MyTag>
        <MyTag id="fitness">
          Fitness center
        </MyTag>
        <MyTag id="parking">
          Parking
        </MyTag>
        <MyTag id="pool">
          Swimming pool
        </MyTag>
        <MyTag id="breakfast">
          Breakfast
        </MyTag>
      </MyTagGroup>
      <p>
        Current selection
        (controlled):
        {' '}
        {selected ===
            'all'
          ? 'all'
          : [...selected]
            .join(', ')}
      </p>
    </>
  );
}

Tags may be links to another page or website. This can be achieved by passing the href prop to the <Tag> component. Tags with an href are not selectable.

<MyTagGroup label="Links">
  <MyTag href="https://adobe.com/" target="_blank">Adobe</MyTag>
  <MyTag href="https://apple.com/" target="_blank">Apple</MyTag>
  <MyTag href="https://google.com/" target="_blank">Google</MyTag>
  <MyTag href="https://microsoft.com/" target="_blank">Microsoft</MyTag>
</MyTagGroup>
<MyTagGroup label="Links">
  <MyTag href="https://adobe.com/" target="_blank">
    Adobe
  </MyTag>
  <MyTag href="https://apple.com/" target="_blank">
    Apple
  </MyTag>
  <MyTag href="https://google.com/" target="_blank">
    Google
  </MyTag>
  <MyTag href="https://microsoft.com/" target="_blank">
    Microsoft
  </MyTag>
</MyTagGroup>
<MyTagGroup label="Links">
  <MyTag
    href="https://adobe.com/"
    target="_blank"
  >
    Adobe
  </MyTag>
  <MyTag
    href="https://apple.com/"
    target="_blank"
  >
    Apple
  </MyTag>
  <MyTag
    href="https://google.com/"
    target="_blank"
  >
    Google
  </MyTag>
  <MyTag
    href="https://microsoft.com/"
    target="_blank"
  >
    Microsoft
  </MyTag>
</MyTagGroup>

Client side routing#

The <Tag> component works with frameworks and client side routers like Next.js and React Router. As with other React Aria components that support links, this works via the RouterProvider component at the root of your app. See the client side routing guide to learn how to set this up.

Disabled tags#


TagGroup supports marking items as disabled using the disabledKeys prop. Each key in this list corresponds with the id prop passed to the Tag component, or automatically derived from the values passed to the items prop. Disabled items are not focusable, selectable, or keyboard navigable. See the Collections guide for more details.

<MyTagGroup
 label="Sandwich contents"
 selectionMode="multiple"
 disabledKeys={['tuna']}>
  <MyTag id="lettuce">Lettuce</MyTag>
  <MyTag id="tomato">Tomato</MyTag>
  <MyTag id="cheese">Cheese</MyTag>
  <MyTag id="tuna">Tuna Salad</MyTag>
  <MyTag id="egg">Egg Salad</MyTag>
  <MyTag id="ham">Ham</MyTag>
</MyTagGroup>
<MyTagGroup
 label="Sandwich contents"
 selectionMode="multiple"
 disabledKeys={['tuna']}>
  <MyTag id="lettuce">Lettuce</MyTag>
  <MyTag id="tomato">Tomato</MyTag>
  <MyTag id="cheese">Cheese</MyTag>
  <MyTag id="tuna">Tuna Salad</MyTag>
  <MyTag id="egg">Egg Salad</MyTag>
  <MyTag id="ham">Ham</MyTag>
</MyTagGroup>
<MyTagGroup
  label="Sandwich contents"
  selectionMode="multiple"
  disabledKeys={[
    'tuna'
  ]}>
  <MyTag id="lettuce">
    Lettuce
  </MyTag>
  <MyTag id="tomato">
    Tomato
  </MyTag>
  <MyTag id="cheese">
    Cheese
  </MyTag>
  <MyTag id="tuna">
    Tuna Salad
  </MyTag>
  <MyTag id="egg">
    Egg Salad
  </MyTag>
  <MyTag id="ham">
    Ham
  </MyTag>
</MyTagGroup>
Show CSS
.react-aria-TagList {
  .react-aria-Tag {
    &[data-disabled] {
      border-color: var(--border-color-disabled);
      color: var(--text-color-disabled);
    }
  }
}
.react-aria-TagList {
  .react-aria-Tag {
    &[data-disabled] {
      border-color: var(--border-color-disabled);
      color: var(--text-color-disabled);
    }
  }
}
.react-aria-TagList {
  .react-aria-Tag {
    &[data-disabled] {
      border-color: var(--border-color-disabled);
      color: var(--text-color-disabled);
    }
  }
}

Empty state#


Use the renderEmptyState prop to customize what a TagList will display if there are no items.

<TagGroup>
  <Label>Categories</Label>
  <TagList renderEmptyState={() => 'No categories.'}>    {[]}
  </TagList>
</TagGroup>
<TagGroup>
  <Label>Categories</Label>
  <TagList renderEmptyState={() => 'No categories.'}>    {[]}
  </TagList>
</TagGroup>
<TagGroup>
  <Label>
    Categories
  </Label>
  <TagList
    renderEmptyState={() =>
      'No categories.'}
  >    {[]}
  </TagList>
</TagGroup>

Help text#


Description#

The description slot can be used to associate additional help text with a TagGroup.

<TagGroup>
  <Label>Categories</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <Text slot="description">Your selected categories.</Text></TagGroup>
<TagGroup>
  <Label>Categories</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <Text slot="description">Your selected categories.</Text></TagGroup>
<TagGroup>
  <Label>
    Categories
  </Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <Text slot="description">
    Your selected
    categories.
  </Text></TagGroup>
Show CSS
.react-aria-TagGroup {
  [slot=description] {
    font-size: 12px;
  }

  [slot=errorMessage] {
    font-size: 12px;
    color: var(--invalid-color);
  }
}
.react-aria-TagGroup {
  [slot=description] {
    font-size: 12px;
  }

  [slot=errorMessage] {
    font-size: 12px;
    color: var(--invalid-color);
  }
}
.react-aria-TagGroup {
  [slot=description] {
    font-size: 12px;
  }

  [slot=errorMessage] {
    font-size: 12px;
    color: var(--invalid-color);
  }
}

Error message#

The errorMessage slot can be used to help the user fix a validation error.

<TagGroup>
  <Label>Categories</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <Text slot="errorMessage">Invalid set of categories.</Text></TagGroup>
<TagGroup>
  <Label>Categories</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <Text slot="errorMessage">
    Invalid set of categories.
  </Text></TagGroup>
<TagGroup>
  <Label>
    Categories
  </Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <Text slot="errorMessage">
    Invalid set of
    categories.
  </Text></TagGroup>

Props#


TagGroup#

NameTypeDescription
selectionBehaviorSelectionBehaviorHow multiple selection should behave in the collection.
disabledKeysIterable<Key>The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
selectionModeSelectionModeThe type of selection that is allowed in the collection.
disallowEmptySelectionbooleanWhether the collection allows empty selection.
selectedKeys'all'Iterable<Key>The currently selected keys in the collection (controlled).
defaultSelectedKeys'all'Iterable<Key>The initial selected keys in the collection (uncontrolled).
childrenReactNodeThe children of the component.
classNamestringThe CSS className for the element.
styleCSSPropertiesThe inline style for the element.
Events
NameTypeDescription
onRemove( (keys: Set<Key> )) => voidHandler that is called when a user deletes a tag.
onSelectionChange( (keys: Selection )) => anyHandler that is called when the selection changes.
Layout
NameTypeDescription
slotstringnull

A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.

Accessibility
NameTypeDescription
idstringThe element's unique identifier. See MDN.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

TagList#

A <TagList> is a list of tags within a <TagGroup>.

NameTypeDescription
renderEmptyState( (props: TagListRenderProps )) => ReactNodeProvides content to display when there are no items in the tag list.
childrenReactNode( (item: T )) => ReactNodeThe contents of the collection.
dependenciesany[]Values that should invalidate the item cache when using dynamic collections.
itemsIterable<T>Item objects in the collection.
classNamestring( (values: TagListRenderProps )) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: TagListRenderProps )) => CSSPropertiesThe inline style for the element. A function may be provided to compute the style based on component state.

Tag#

A <Tag> defines a single item within a <TagList>. If the children are not plain text, then the textValue prop must also be set to a plain text representation for accessibility.

NameTypeDescription
idKeyA unique id for the tag.
textValuestring

A string representation of the tags's contents, used for accessibility. Required if children is not a plain text string.

isDisabledbooleanWhether the tag is disabled.
childrenReactNode( (values: TagRenderProps )) => ReactNodeThe children of the component. A function may be provided to alter the children based on component state.
classNamestring( (values: TagRenderProps )) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: TagRenderProps )) => CSSPropertiesThe inline style for the element. A function may be provided to compute the style based on component state.
hrefHrefA URL to link to. See MDN.
hrefLangstringHints at the human language of the linked URL. SeeMDN.
targetHTMLAttributeAnchorTargetThe target window for the link. See MDN.
relstringThe relationship between the linked resource and the current page. See MDN.
downloadbooleanstringCauses the browser to download the linked URL. A string may be provided to suggest a file name. See MDN.
pingstringA space-separated list of URLs to ping when the link is followed. See MDN.
referrerPolicyHTMLAttributeReferrerPolicyHow much of the referrer to send when following the link. See MDN.
routerOptionsRouterOptionsOptions for the configured client side router.

Label#

A <Label> accepts all HTML attributes.

Text#

<Text> accepts all HTML attributes.

Styling#


React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

.react-aria-TagGroup {
  /* ... */
}
.react-aria-TagGroup {
  /* ... */
}
.react-aria-TagGroup {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

<TagGroup className="my-tag-group">
  {/* ... */}
</TagGroup>
<TagGroup className="my-tag-group">
  {/* ... */}
</TagGroup>
<TagGroup className="my-tag-group">
  {/* ... */}
</TagGroup>

In addition, some components support multiple UI states (e.g. pressed, hovered, etc.). React Aria components expose states using data attributes, which you can target in CSS selectors. For example:

.react-aria-Tag[data-selected] {
  /* ... */
}

.react-aria-Tag[data-focused] {
  /* ... */
}
.react-aria-Tag[data-selected] {
  /* ... */
}

.react-aria-Tag[data-focused] {
  /* ... */
}
.react-aria-Tag[data-selected] {
  /* ... */
}

.react-aria-Tag[data-focused] {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

<Tag
  className={({ isSelected }) => isSelected ? 'bg-blue-400' : 'bg-gray-100'}
>
  Item
</Tag>
<Tag
  className={({ isSelected }) =>
    isSelected ? 'bg-blue-400' : 'bg-gray-100'}
>
  Item
</Tag>
<Tag
  className={(
    { isSelected }
  ) =>
    isSelected
      ? 'bg-blue-400'
      : 'bg-gray-100'}
>
  Item
</Tag>

Render props may also be used as children to alter what elements are rendered based on the current state. For example, you could render a remove button only when removal is allowed.

<Tag>
  {({allowsRemoving}) => (
    <>
      Item
      {allowsRemoving && <RemoveButton />}
    </>
  )}
</Tag>
<Tag>
  {({allowsRemoving}) => (
    <>
      Item
      {allowsRemoving && <RemoveButton />}
    </>
  )}
</Tag>
<Tag>
  {(
    { allowsRemoving }
  ) => (
    <>
      Item
      {allowsRemoving &&
        (
          <RemoveButton />
        )}
    </>
  )}
</Tag>

The states and selectors for each component used in a TagGroup are documented below.

TagGroup#

A TagGroup can be targeted with the .react-aria-TagGroup CSS selector, or by overriding with a custom className.

TagList#

A TagList can be targeted with the .react-aria-TagList CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
isEmpty[data-empty]Whether the tag list has no items and should display its empty state.
isFocused[data-focused]Whether the tag list is currently focused.
isFocusVisible[data-focus-visible]Whether the tag list is currently keyboard focused.
stateState of the TagGroup.

Tag#

A Tag can be targeted with the .react-aria-Tag CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
allowsRemoving[data-allows-removing]Whether the tag group allows items to be removed.
isHovered[data-hovered]Whether the item is currently hovered with a mouse.
isPressed[data-pressed]Whether the item is currently in a pressed state.
isSelected[data-selected]Whether the item is currently selected.
isFocused[data-focused]Whether the item is currently focused.
isFocusVisible[data-focus-visible]Whether the item is currently keyboard focused.
isDisabled[data-disabled]

Whether the item is non-interactive, i.e. both selection and actions are disabled and the item may not be focused. Dependent on disabledKeys and disabledBehavior.

selectionMode[data-selection-mode="single | multiple"]The type of selection that is allowed in the collection.
selectionBehaviorThe selection behavior for the collection.

Tags also accept a <Button slot="remove"> element as a child, which allows them to be removed. This can be conditionally rendered using the allowsRemoving render prop, as shown below.

Label#

A Label can be targeted with the .react-aria-Label CSS selector, or by overriding with a custom className.

Text#

The help text elements within a TagGroup can be targeted with the [slot=description] and [slot=errorMessage] CSS selectors, or by adding a custom className.

Advanced customization#


Composition#

If you need to customize one of the components within a TagGroup, such as TagList or Tag, in many cases you can create a wrapper component. This lets you customize the props passed to the component.

function MyTag(props) {
  return <Tag {...props} className="my-tag" />
}
function MyTag(props) {
  return <Tag {...props} className="my-tag" />
}
function MyTag(props) {
  return (
    <Tag
      {...props}
      className="my-tag"
    />
  );
}

Contexts#

All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).

ComponentContextPropsRef
TagGroupTagGroupContextTagGroupPropsHTMLDivElement

This example shows a component that accepts a TagGroup and a ToggleButton as children, and allows the user to turn edit mode for the tag group on and off by pressing the button.

import {ToggleButtonContext, TagGroupContext} from 'react-aria-components';

function Removable({children, onRemove}) {
  let [isSelected, onChange] = React.useState(false);
  return (
    <ToggleButtonContext.Provider value={{isSelected, onChange}}>
      <TagGroupContext.Provider value={{onRemove: isSelected && onRemove}}>        {children}
      </TagGroupContext.Provider>
    </ToggleButtonContext.Provider>
  );
}
import {
  TagGroupContext,
  ToggleButtonContext
} from 'react-aria-components';

function Removable({ children, onRemove }) {
  let [isSelected, onChange] = React.useState(false);
  return (
    <ToggleButtonContext.Provider
      value={{ isSelected, onChange }}
    >
      <TagGroupContext.Provider
        value={{ onRemove: isSelected && onRemove }}
      >        {children}
      </TagGroupContext.Provider>
    </ToggleButtonContext.Provider>
  );
}
import {
  TagGroupContext,
  ToggleButtonContext
} from 'react-aria-components';

function Removable(
  { children, onRemove }
) {
  let [
    isSelected,
    onChange
  ] = React.useState(
    false
  );
  return (
    <ToggleButtonContext.Provider
      value={{
        isSelected,
        onChange
      }}
    >
      <TagGroupContext.Provider
        value={{
          onRemove:
            isSelected &&
            onRemove
        }}
      >        {children}
      </TagGroupContext.Provider>
    </ToggleButtonContext.Provider>
  );
}

The Removable component can be reused to make the edit mode of any nested TagGroup controlled by a ToggleButton.

import {ToggleButton} from 'react-aria-components';

<Removable onRemove={ids => alert(`Remove ${[...ids]}`)}>
  <MyTagGroup label="Ice cream flavor">
    <MyTag id="chocolate">Chocolate</MyTag>
    <MyTag id="mint">Mint</MyTag>
    <MyTag id="strawberry">Strawberry</MyTag>
    <MyTag id="vanilla">Vanilla</MyTag>
  </MyTagGroup>
  <ToggleButton style={{marginTop: '8px'}}>Edit</ToggleButton>
</Removable>
import {ToggleButton} from 'react-aria-components';

<Removable
  onRemove={(ids) => alert(`Remove ${[...ids]}`)}
>
  <MyTagGroup label="Ice cream flavor">
    <MyTag id="chocolate">Chocolate</MyTag>
    <MyTag id="mint">Mint</MyTag>
    <MyTag id="strawberry">Strawberry</MyTag>
    <MyTag id="vanilla">Vanilla</MyTag>
  </MyTagGroup>
  <ToggleButton style={{ marginTop: '8px' }}>
    Edit
  </ToggleButton>
</Removable>
import {ToggleButton} from 'react-aria-components';

<Removable
  onRemove={(ids) =>
    alert(
      `Remove ${[
        ...ids
      ]}`
    )}
>
  <MyTagGroup label="Ice cream flavor">
    <MyTag id="chocolate">
      Chocolate
    </MyTag>
    <MyTag id="mint">
      Mint
    </MyTag>
    <MyTag id="strawberry">
      Strawberry
    </MyTag>
    <MyTag id="vanilla">
      Vanilla
    </MyTag>
  </MyTagGroup>
  <ToggleButton
    style={{
      marginTop: '8px'
    }}
  >
    Edit
  </ToggleButton>
</Removable>

Custom children#

TagGroup passes props to its child components, such as the label, via their associated contexts. These contexts are exported so you can also consume them in your own custom components. This enables you to reuse existing components from your app or component library together with React Aria Components.

ComponentContextPropsRef
LabelLabelContextLabelPropsHTMLLabelElement
ButtonButtonContextButtonPropsHTMLButtonElement
TextTextContextTextPropsHTMLElement

This example consumes from LabelContext in an existing styled label component to make it compatible with React Aria Components. The useContextProps hook merges the local props and ref with the ones provided via context by TagGroup.

import type {LabelProps} from 'react-aria-components';
import {LabelContext, useContextProps} from 'react-aria-components';

const MyCustomLabel = React.forwardRef(
  (props: LabelProps, ref: React.ForwardedRef<HTMLLabelElement>) => {
    // Merge the local props and ref with the ones provided via context.
    [props, ref] = useContextProps(props, ref, LabelContext);
    // ... your existing Label component
    return <label {...props} ref={ref} />;
  }
);
import type {LabelProps} from 'react-aria-components';
import {
  LabelContext,
  useContextProps
} from 'react-aria-components';

const MyCustomLabel = React.forwardRef(
  (
    props: LabelProps,
    ref: React.ForwardedRef<HTMLLabelElement>
  ) => {
    // Merge the local props and ref with the ones provided via context.
    [props, ref] = useContextProps(
      props,
      ref,
      LabelContext
    );
    // ... your existing Label component
    return <label {...props} ref={ref} />;
  }
);
import type {LabelProps} from 'react-aria-components';
import {
  LabelContext,
  useContextProps
} from 'react-aria-components';

const MyCustomLabel =
  React.forwardRef(
    (
      props: LabelProps,
      ref:
        React.ForwardedRef<
          HTMLLabelElement
        >
    ) => {
      // Merge the local props and ref with the ones provided via context.
      [props, ref] =
        useContextProps(
          props,
          ref,
          LabelContext
        );
      // ... your existing Label component
      return (
        <label
          {...props}
          ref={ref}
        />
      );
    }
  );

Now you can use MyCustomLabel within a TagGroup, in place of the builtin React Aria Components Label.

<TagGroup>
  <MyCustomLabel>Name</MyCustomLabel>  {/* ... */}
</TagGroup>
<TagGroup>
  <MyCustomLabel>Name</MyCustomLabel>  {/* ... */}
</TagGroup>
<TagGroup>
  <MyCustomLabel>
    Name
  </MyCustomLabel>  {/* ... */}
</TagGroup>

State#

TagGroup provides a ListState object to its children via ListStateContext. This can be used to access and manipulate the TagGroup's state.

This example shows a SelectionCount component that can be placed within a TagGroup to display the number of selected tags.

import {ListStateContext} from 'react-aria-components';

function SelectionCount() {
  let state = React.useContext(ListStateContext)!;  let selected = state.selectionManager.selectedKeys.size;
  return <small>{selected} tags selected.</small>;
}

<TagGroup selectionMode="multiple">
  <Label>Tags</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <SelectionCount />
</TagGroup>
import {ListStateContext} from 'react-aria-components';

function SelectionCount() {
  let state = React.useContext(ListStateContext)!;  let selected = state.selectionManager.selectedKeys.size;
  return <small>{selected} tags selected.</small>;
}

<TagGroup selectionMode="multiple">
  <Label>Tags</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <SelectionCount />
</TagGroup>
import {ListStateContext} from 'react-aria-components';

function SelectionCount() {
  let state = React
    .useContext(
      ListStateContext
    )!;  let selected =
    state
      .selectionManager
      .selectedKeys.size;
  return (
    <small>
      {selected}{' '}
      tags selected.
    </small>
  );
}

<TagGroup selectionMode="multiple">
  <Label>Tags</Label>
  <TagList>
    <Tag>News</Tag>
    <Tag>Travel</Tag>
    <Tag>Gaming</Tag>
    <Tag>Shopping</Tag>
  </TagList>
  <SelectionCount />
</TagGroup>

Hooks#

If you need to customize things even further, such as accessing internal state, intercepting events, or customizing DOM structure, you can drop down to the lower level Hook-based API. See useTagGroup for more details.