Tag Group
A set of removable chips or tokens.
Installation
Included in poetry-ui — available as
poetry_tag_group
the moment you've installed Poetry,
with no per-component step. To own the source and edit it, copy it into your app:
bin/rails g poetry:add tag-group
Default
<%= poetry_tag_group(label: "Topics") do |group| %>
<% group.with_tag(value: "rails") { "Rails" } %>
<% group.with_tag(value: "hotwire") { "Hotwire" } %>
<% group.with_tag(value: "ruby") { "Ruby" } %>
<% end %>
As form value
<%# name: serializes one hidden topics[] input per tag; removal is
cancelable (poetry:tag-group:remove) for Turbo-owned re-renders. %>
<%= poetry_tag_group(name: "post[topics]", label: "Topics") do |group| %>
<% group.with_tag(value: "news") { "News" } %>
<% group.with_tag(value: "release") { "Release" } %>
<% end %>
API
Poetry::Ui::TagGroup::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| described_by: | String | Space-separated hint/error ids for the GRID (the labelled element) - a raw aria-describedby in html_attributes would land on the outer wrapper div, unassociated for AT. | |
| label: | String | required | The grid's accessible name, rendered as a caption span. Required. |
| name: | String | Makes the group a form value: one hidden <name>[] input submits per tag. |
Slots
| Writer | Description |
|---|---|
| with_tag | Declares one chip. value: is its identity (and form value); label: is the accessible name and the visible text when no block is given; removable: false drops the remove button. |
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=tag-group] | The labelled wrapper - caption span + grid stack here |
| [data-slot=tag-group-label] | The caption span (label:), wired via aria-labelledby (a grid is not a labelable form control - never a <label>) |
| [data-slot=tag-group-grid] | The tag collection (role=grid; role=group + the tab stop when empty) - roving focus, removal keys, and the focus-scoped live region ride here |
| [data-slot=tag-group-tag] | One chip (role=row > gridcell): content, the remove button, and - in form mode - the hidden name[] input |
| [data-slot=tag-group-remove] | The per-tag remove button - tabbable (Tab steps from the row into it), removes exactly its own tag |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| tag-group-grid | data-empty | no tags remain (controller-kept after removals) | — |
| tag-group-tag | data-disabled | the tag is disabled (skipped by arrows and removal) | — |
| tag-group-tag | data-value | always - the tag's value (the remove event's detail and the hidden input's value) | — |
Wiring
The Stimulus surface each element carries — declared in the component, verified against rendered DOM in CI. Bare actions fire on the element's default event.
| Element | Controller | Wiring |
|---|---|---|
| grid | poetry--core--tag-group | registers · keydown on keydown |
| grid | poetry--core--roving-focus | registers · value orientation · value loop · keydown on keydown |
| remove | poetry--core--tag-group | remove on click |