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).
import { RuiTagGroup, RuiTagList, RuiTag } from '@ecopages/radiant-ui/tag-group';
<RuiTagGroup value={['react', 'typescript']} selectionMode="multiple" label="Skills">
<RuiTagList>
<RuiTag value="react" label="React">React</RuiTag>
<RuiTag value="typescript" label="TypeScript">TypeScript</RuiTag>
</RuiTagList>
</RuiTagGroup>Custom markup
<rui-tag-group> coordinates any light-DOM tree that matches its query contract. The Rui* helpers stamp these targets; they are not required.
import '@ecopages/radiant-ui/tag-group';
<rui-tag-group value="react" label="Skills">
<div data-tag-list class="rui-tag-group__list">
<span data-tag data-value="react" data-label="React" class="rui-tag">
React
<button type="button" data-tag-remove class="rui-tag__remove"></button>
</span>
</div>
</rui-tag-group>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 (<rui-tag-group>) 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 | <rui-tag-group> + [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
labelwhen not described by surrounding text.