TagGroup

Keyboard-navigable list of tags built on React Aria, with optional selection and removal. Arrow keys move between tags, so the group is a single tab stop rather than one per tag.

import { TagGroup } from "@pockets-ui/components"

Anatomy

PartRole
TagGroup.RootOwns selectionMode, selectedKeys, disabledKeys, onRemove.
TagGroup.LabelThe group's visible label.
TagGroup.ListThe tag container. Takes static children or items.
TagGroup.TagOne tag. Needs an id when declared statically.
TagGroup.StaticTagA display-only tag with an intent wash. Never selected or removed.

Selection

selectionMode opts the group into "single" or "multiple" selection. Without it, tags are display-only.

Amenities
Laundry
Fitness center
Parking
Swimming pool
Breakfast
Code
import { TagGroup } from "@pockets-ui/components"

export default function TagGroupSelection() {
  return (
    <TagGroup.Root
      selectionMode="multiple"
      defaultSelectedKeys={["laundry", "pool"]}
    >
      <TagGroup.Label>Amenities</TagGroup.Label>
      <TagGroup.List>
        <TagGroup.Tag id="laundry">Laundry</TagGroup.Tag>
        <TagGroup.Tag id="fitness">Fitness center</TagGroup.Tag>
        <TagGroup.Tag id="parking" isDisabled>
          Parking
        </TagGroup.Tag>
        <TagGroup.Tag id="pool">Swimming pool</TagGroup.Tag>
        <TagGroup.Tag id="breakfast">Breakfast</TagGroup.Tag>
      </TagGroup.List>
    </TagGroup.Root>
  )
}

Removable tags

Passing onRemove gives every tag a remove button and enables the Delete and Backspace keys. The group doesn't hold the list — remove the item from your own state and re-render, as this example does.

Interests
News
Travel
Gaming
Shopping
Code
import { TagGroup } from "@pockets-ui/components"
import { useState } from "react"

const INITIAL_INTERESTS = [
  { id: "news", name: "News" },
  { id: "travel", name: "Travel" },
  { id: "gaming", name: "Gaming" },
  { id: "shopping", name: "Shopping" },
]

export default function TagGroupRemovable() {
  const [interests, setInterests] = useState(INITIAL_INTERESTS)

  return (
    // Passing onRemove to Root makes every tag grow a remove button.
    <TagGroup.Root
      onRemove={(keys) =>
        setInterests((items) => items.filter((item) => !keys.has(item.id)))
      }
    >
      <TagGroup.Label>Interests</TagGroup.Label>
      <TagGroup.List items={interests}>
        {(item) => <TagGroup.Tag>{item.name}</TagGroup.Tag>}
      </TagGroup.List>
    </TagGroup.Root>
  )
}

Static tags

TagGroup.StaticTag is a presentational chip. It sits in the same list as regular tags (mix them freely, including inside an items render function) but never takes part in selection or removal, and isn't a tab stop. Its intent picks a wash from the semantic bg / fg intents: neutral (default), action, info, success, warning, danger, brand, or premium.

Listing status
New
Verified
Offer pending
Inspection due
Expired
Pro
Code
import { TagGroup } from "@pockets-ui/components"

export default function TagGroupStatic() {
  return (
    <TagGroup.Root>
      <TagGroup.Label>Listing status</TagGroup.Label>
      <TagGroup.List>
        <TagGroup.StaticTag id="new">New</TagGroup.StaticTag>
        <TagGroup.StaticTag id="verified" intent="success">
          Verified
        </TagGroup.StaticTag>
        <TagGroup.StaticTag id="offer" intent="info">
          Offer pending
        </TagGroup.StaticTag>
        <TagGroup.StaticTag id="inspection" intent="warning">
          Inspection due
        </TagGroup.StaticTag>
        <TagGroup.StaticTag id="expired" intent="danger">
          Expired
        </TagGroup.StaticTag>
        <TagGroup.StaticTag id="pro" intent="premium">
          Pro
        </TagGroup.StaticTag>
      </TagGroup.List>
    </TagGroup.Root>
  )
}