---
title: Tag Group
description: Tag groups display selected values as removable chips, commonly used in multi-select fields and filter bars.
category: Data display
---
import { meta as TagGroupMeta, Default } from '@/content/stories/tag-group';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';
export const config = {
dependencies: {
components: [Canvas, Demo],
scripts: [
'../../components/component-docs/demo.script.tsx',
'../../components/component-docs/canvas.script.tsx',
'../../components/component-docs/controls.script.tsx',
],
},
};
# Tag Group
Tag groups display selected values as removable chips, commonly used in multi-select fields and filter bars.
## Try it
## Usage
The live `value` property is always a `string[]` (`[]` when nothing is selected). The HTML attribute is those tokens as a comma-separated string. JSX may pass a string or an array; a string is parsed as CSV. `rui-change` emits `{ value: string[] }`. Pass `tags` for the simple API, or compose `RuiTagList` and `RuiTag`. `RuiTag` already includes the remove control.
Not form-associated. Wrap in `RuiField` and read the value from `RuiForm` `onSubmit`. It does not appear in `new FormData(form)`.
```tsx
import { RuiTagGroup, RuiTagList, RuiTag } from '@ecopages/radiant-ui/tag-group';
ReactTypeScript
```
## Custom markup
`` coordinates any light-DOM tree that matches its query contract. The `Rui*` helpers stamp these targets; they are not required.
```tsx
import '@ecopages/radiant-ui/tag-group';
React
```
BEM classes are presentation-only. Omit `[data-tag-remove]` for a non-removable tag. Add `[data-ref="root"]` around the list only if you call `setItems()`.
## Embedded in selects
Set `embedded` when tags render inside a select or combobox trigger rather than standalone. Selection is disabled because the parent owns the value; removal still emits `rui-remove`.
## Theming
Tag Group surfaces map to **semantic surface roles**, never Tailwind palette steps:
| Part | CSS roles |
| --- | --- |
| Tag (`rui-tag`) | `surface`, `border`, `on-background`, `rounded-pill` |
| Selected tag (`rui-tag[aria-selected='true']`) | `primary`, `on-primary` |
| Remove control (`rui-tag__remove`) | `currentColor`; hover `on-background` |
| Disabled tag | `--opacity-muted` |
Override roles at the theme layer, not in component CSS.
## API
`RuiTagGroup` is a custom element (``) wrapping a focusable list of tags. Compose `RuiTagList` and `RuiTag`, pass `tags` to the helper, or stamp the light-DOM contract directly. It is not form-associated.
### Attributes
| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | omitted | Comma-separated selected tokens. Empty selection removes the attribute. The JS property is `string[]`. |
| `label` | `string` | `''` | Accessible name for the tag list. |
| `disabled` | `boolean` | `false` | Disable selection and removal. |
| `selection-mode` | `single` ยท `multiple` | `multiple` | Allow one or many selected tags. |
| `embedded` | `boolean` | `false` | Disable selection when a parent component owns the selected values. |
### Light-DOM contract
| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `[data-tag-list]` | yes | `id`, `role="list"`, `aria-label`, `aria-disabled` | the list node |
| `[data-tag]` | yes | `id` if missing, `role="listitem"`, `aria-selected`, roving `tabIndex` | `data-value`, `data-label`, `hidden`, `aria-disabled` |
| `[data-value]` | per tag | โ | selection identity; fallback trimmed text |
| `[data-label]` | per tag | โ | remove accessible name; fallback trimmed text |
| `[data-tag-remove]` | no | `type="button"`, `tabIndex="-1"`, `aria-label` | presence |
| `[data-ref="root"]` | for `setItems()` | โ | wrapper around the list |
Do not set `role`, `aria-selected`, or `tabIndex` on tags. Nested hosts: none.
### Events
| Event | Detail | Description |
| --- | --- | --- |
| `rui-change` | `{ value: string[] }` | Emitted when selection changes. |
| `rui-remove` | `{ value: string }` | Emitted when a tag is removed; `value` is the removed tag's value. Also followed by `rui-change`. |
### Methods
| Method | Description |
| --- | --- |
| `resync()` | Re-read authored `[data-tag]` children after in-place DOM mutations. |
| `setItems(items)` | Replace authored tags with a host-owned list. Requires `[data-ref="root"]`. |
### View helpers
| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiTagGroup` | `` + `[data-ref="root"]` | Accepts `tags` (`RuiTagData[]`) or children. |
| `RuiTagList` | `[data-tag-list]` | Flex-wrap container. |
| `RuiTag` | `[data-tag]`, `data-value`, `data-label` | Always includes `RuiTagRemove`. |
| `RuiTagRemove` | `[data-tag-remove]` | Host fills the accessible name on connect. |
### CSS classes
Public BEM classes on the composed light-DOM surface (documented via `@cssclass`):
| Class | Description |
| --- | --- |
| `.rui-tag-group` | Root wrapper around the tag list. |
| `.rui-tag-group__list` | Tag row (flex-wrap container). |
| `.rui-tag` | Tag chip; selected state via `[aria-selected='true']`. |
| `.rui-tag__remove` | Tag remove control. |
### Theme roles
| Part | CSS variables consumed |
| --- | --- |
| Tag | `--surface`, `--border`, `--on-background`, `--radius-pill` |
| Selected tag | `--primary`, `--on-primary` |
| Remove hover | `--on-background` |
| Disabled | `--opacity-muted` |
| Focus ring | `--focus-ring` |
## Accessibility
- Each tag exposes its label text to screen readers.
- Remove buttons have accessible names indicating which tag will be removed (`Remove {label}`).
- The group has an accessible name via `label` when not described by surrounding text.