DisplayReact
A set of tags that are shown, removed or selected, with no text field.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<TagGroup.Root>
<TagGroup.Tag>
<TagGroup.TagContent>
<TagGroup.TagText />
<TagGroup.TagRemove />
</TagGroup.TagContent>
</TagGroup.Tag>
<TagGroup.Empty />
</TagGroup.Root>Examples
Filters
Status filters with selectable and multiple: a press or Space toggles one, a selected tag shows a check, and Archived is disabled.
API reference
23 props across 6 parts, in React syntax. className, style, render are listed once, under Shared props.
EmptyTagGroup.Empty
Content shown while the group has no tags. Renders nothing otherwise. It has no ARIA role. Renders a <div>.
RootTagGroup.Root
Groups the tags the app shows: a grid with one row per tag, a group while there are none, with removal through onRemove and Grid List's selection., followed by the status region. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
valueThe selected value. | TagGroupInputValue<Value, Multiple> | null | — |
defaultValueThe uncontrolled selected value when the group is initially rendered. | TagGroupInputValue<Value, Multiple> | null | — |
onValueChangeEvent handler called when the selection changes, once per gesture. | ((value: TagGroupOutputValue<Value, Multiple>, eventDetails: TagGroupRootChangeEventDetails) => void) | — |
selectableWhether tags can be selected. | boolean | false |
multipleWhether several tags can be selected. | Multiple | false |
disallowEmptySelectionWhether the last selected tag of a multiple group cannot be deselected. | boolean | false |
selectOnPressWhether a press on a selectable tag selects it. | boolean | true |
isItemEqualToValueCustom comparison logic used to determine if a tag value matches the current value. | ((itemValue: Value, value: Value) => boolean) | — |
onRemoveEvent handler called with the values of the tags to remove, in document order: a removal | ((values: Value[], eventDetails: TagGroupRootRemoveEventDetails) => void) | — |
allowMoveWhether tags can be moved by dragging and with Ctrl/Cmd+X then Ctrl/Cmd+V. | boolean | false |
canMoveWhether a move may happen. | ((details: GridListMoveDetails<Value>) => boolean) | — |
onItemMoveCalled with a move. | ((details: GridListMoveDetails<Value>, eventDetails: TagGroupRootItemMoveEventDetails) => void) | — |
loopFocusWhether the arrow keys wrap from the last tag to the first and back. | boolean | false |
disabledWhether the component should ignore user interaction. | boolean | false |
labelsThe texts of the status region. | Partial<TagGroupLabels> | — |
| Data attribute | Description |
|---|---|
data-disabled | Present when the group is disabled. |
data-empty | Present when the group has no tags. |
data-selectable | Present when tags can be selected. |
data-multiple | Present when several tags can be selected. |
data-moving | Present while a move is pending. |
TagTagGroup.Tag
A tag: a row of the group with its one cell (TagGroup.TagContent). Renders a <div>.
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
valuerequiredA unique value that identifies this tag. | any | — |
labelThe name the announcements and typeahead read. | string | — |
disabledWhether the tag should ignore user interaction. | boolean | false |
removableWhether the tag can be removed by a key or by TagGroup.TagRemove. | boolean | true |
selectableWhether the tag can be selected, in a group that is selectable. | boolean | true |
onActionCalled when the tag is activated: Enter, or a press on a tag that does not select. | ((eventDetails: GridListActionDetails) => void) | — |
| Data attribute | Description |
|---|---|
data-selected | Present when the tag is selected. |
data-highlighted | Present when focus is on the tag or inside it. |
data-disabled | Present when the tag is disabled. |
data-removable | Present when the tag can be removed. |
data-moving | Present while the tag is being moved. |
data-drop-target | Where a dragged tag would land relative to this one. |
data-dragging | Present while the tag is being dragged. |
TagContentTagGroup.TagContent
The one cell of a tag: its text and its remove button. Renders a <div>.
TagRemoveTagGroup.TagRemove
A button that removes its tag. Named "Remove" and the tag's text. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
TagTextTagGroup.TagText
The text of a tag: it names the tag. Renders String(value) when it has no children. Renders a <span>.
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| ArrowRightArrowLeft | Moves to the next or previous tag and stops at the ends | Tag |
| ControlHomeControlEnd | Moves to the first or last tag | Tag |
| Moves to the next tag whose text starts with the typed characters | Tag | |
| Space | With selectable, selects or clears the focused tag | Tag |
| ControlA | With multiple, selects every enabled tag | Tag |
| Enter | Calls the tag's onAction | Tag |
| DeleteBackspace | Calls onRemove with the focused tag, then focuses the next one | Tag |
| Tab | From a tag, reaches its remove button, then leaves the group | Tag |
| Enter | Removes the tag and focuses the next one | TagRemove |
| ControlXControlV | With allowMove, picks up the focused tag and calls onItemMove at another | Tag |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="grid" with aria-multiselectable when multiple; role="group" with data-empty while there are no tags | the tags are one composite with one Tab stop; a development build warns without a name |
| Tag | role="row", one tab stop on the first enabled tag; aria-selected when selectable; aria-disabled when disabled | a reader hears which tags are selected and which cannot be changed |
| TagContent | role="gridcell", the row's only element child | each row has exactly one cell, as the grid pattern needs |
| TagText | the row's name, String(value) when it has no children | a tag is read by its text, not by its remove button |
| TagRemove | a button named "Remove {tag}"; aria-disabled without onRemove; a mousedown on it is prevented | the button says what it removes, and a press does not move focus |
| Empty | rendered only while there are no tags | an empty group says so instead of showing nothing |
Focus order
- The group is one Tab stop, on the first enabled tag; the arrow keys and typeahead move between tags.
- Tab from a tag reaches its remove button, then leaves the group.
- A disabled Root has no Tab stop.