--- 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'; React TypeScript ``` ## 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.