Toggle Group
A set of toggle buttons for single or multiple selection.
Installation
Included in poetry-ui — available as
poetry_toggle_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 toggle-group
Default
<%= poetry_toggle_group(type: :multiple, variant: :outline, values: %w[bold],
label: "Text formatting") do |group| %>
<% group.with_item(value: "bold", label: "Toggle bold") { poetry_icon(name: :bold) } %>
<% group.with_item(value: "italic", label: "Toggle italic") { poetry_icon(name: :italic) } %>
<% group.with_item(value: "underline", label: "Toggle underline") { poetry_icon(name: :underline) } %>
<% end %>
Custom
Use font-normal to set the font weight.
<%# Upstream's font-weight selector: size-16 flex-col items inside a
Field, the hint carrying a live <code> readout (the with_hint block
form) that follows poetry:toggle-group:change. %>
<div data-controller="demo-font-weight" class="w-full max-w-md">
<%= poetry_field(id: "font-weight-group", label_text: "Font Weight", group: true) do |field| %>
<% field.with_hint do %>
Use <code class="rounded-md bg-muted px-1 py-0.5 font-mono"
data-demo-font-weight-target="readout">font-normal</code> to set the font weight.
<% end %>
<%= poetry_toggle_group(type: :single, variant: :outline, spacing: 2, size: :lg,
value: "normal",
data: { action: "poetry:toggle-group:change->demo-font-weight#update" },
**field.control_attributes.transform_keys(&:to_sym)) do |group| %>
<% [%w[light Light font-light], %w[normal Normal font-normal], %w[medium Medium font-medium], %w[bold Bold font-bold]].each do |value, title, weight| %>
<% group.with_item(value: value, label: title,
class: "flex size-16 flex-col items-center justify-center rounded-xl") do %>
<span class="text-2xl leading-none <%= weight %>">Aa</span>
<span class="text-xs text-muted-foreground"><%= title %></span>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
Disabled
<%= poetry_toggle_group(type: :single, variant: :outline, value: "left",
label: "Text alignment") do |group| %>
<% group.with_item(value: "left", label: "Align left") { poetry_icon(name: :"text-align-start") } %>
<% group.with_item(value: "center", label: "Align center", disabled: true) do %>
<%= poetry_icon(name: :"text-align-center") %>
<% end %>
<% group.with_item(value: "right", label: "Align right") { poetry_icon(name: :"text-align-end") } %>
<% end %>
Outline
<%# Upstream's toggle-group-outline: the free-standing pair (spacing
defaults to 2 - segmented is the explicit spacing: 0 opt-in). %>
<%= poetry_toggle_group(type: :multiple, variant: :outline, values: %w[all],
label: "Call filter") do |group| %>
<% group.with_item(value: "all", label: "Toggle all") { "All" } %>
<% group.with_item(value: "missed", label: "Toggle missed") { "Missed" } %>
<% end %>
Single
<%= poetry_toggle_group(type: :single, value: "center", label: "Text alignment") do |group| %>
<% group.with_item(value: "left", label: "Align left") { poetry_icon(name: :"text-align-start") } %>
<% group.with_item(value: "center", label: "Align center") { poetry_icon(name: :"text-align-center") } %>
<% group.with_item(value: "right", label: "Align right") { poetry_icon(name: :"text-align-end") } %>
<% end %>
Sizes
<%# Upstream's toggle-group-sizes: the same outline quartet at sm and
default. %>
<div class="flex flex-col items-start gap-4">
<% [{ size: :sm, label: "Sides (small)" }, { size: :default, label: "Sides" }].each do |row| %>
<%= poetry_toggle_group(type: :multiple, size: row[:size], variant: :outline,
values: %w[top], label: row[:label]) do |group| %>
<% %w[Top Bottom Left Right].each do |side| %>
<% group.with_item(value: side.downcase, label: "Toggle #{side.downcase}") { side } %>
<% end %>
<% end %>
<% end %>
</div>
Spaced
<%= poetry_toggle_group(type: :multiple, variant: :outline, spacing: 2,
values: %w[bold italic], label: "Text formatting") do |group| %>
<% group.with_item(value: "bold", label: "Toggle bold") { poetry_icon(name: :bold) } %>
<% group.with_item(value: "italic", label: "Toggle italic") { poetry_icon(name: :italic) } %>
<% group.with_item(value: "underline", label: "Toggle underline") { poetry_icon(name: :underline) } %>
<% end %>
Vertical
<%# Upstream's toggle-group-vertical: multiple + vertical + spacing 1 -
data-vertical flips the root to a column. %>
<%= poetry_toggle_group(type: :multiple, orientation: :vertical, spacing: 1,
values: %w[bold italic], label: "Text formatting") do |group| %>
<% group.with_item(value: "bold", label: "Toggle bold") { poetry_icon(name: :bold) } %>
<% group.with_item(value: "italic", label: "Toggle italic") { poetry_icon(name: :italic) } %>
<% group.with_item(value: "underline", label: "Toggle underline") { poetry_icon(name: :underline) } %>
<% end %>
API
Poetry::Ui::ToggleGroup::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 |
|---|---|---|---|
| size: | Symbol | one of: default, sm, lg; defaults to :default |
The shared Toggle size axis, cascaded from the root to every item (root wins). |
| variant: | Symbol | one of: default, outline; defaults to :default |
The shared Toggle variant axis, cascaded from the root to every item (root wins). |
| disabled: | Boolean | defaults to false |
Disables every item in the group. |
| label: | String | The group's accessible name (aria-label) - a nameless radiogroup/toolbar logs a lint warning. | |
| orientation: | Symbol | one of: horizontal, vertical; defaults to :horizontal |
The roving axis; :vertical stacks the items and flips the arrow keys. |
| spacing: | Integer | defaults to 2 |
0 = the classic segmented control (joined corners, collapsed outline borders); >0 = free-standing items separated by that gap step. |
| type: | Symbol | defaults to :single |
:single keeps at most one item pressed; :multiple toggles items independently. |
| value: | String | single: the pressed item's value. ArgumentError with :multiple. | |
| values: | Array | defaults to -> { [] } |
multiple: the pressed items' values. ArgumentError with :single. |
Slots
| Writer | Description |
|---|---|
| with_item | Declares one item: value: (unique - duplicates raise), label: (required when icon-only), disabled:; the block is the content. Pressed state comes from value:/values:. |
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=toggle-group] | The role=radiogroup (single) / role=toolbar (multiple) root - the value-set machine and roving focus ride here; the axes cascade to items |
| [data-slot=toggle-group-item] | One dumb <button> under the group machine - Toggle-styled, no per-item controller |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| toggle-group | data-variant | the shared Toggle variant (root wins) | default · outline |
| toggle-group | data-size | the shared Toggle size (root wins) | default · sm · lg |
| toggle-group | data-spacing | the gap step - 0 is the segmented chain (joined corners), >0 free-standing | — |
| toggle-group | data-orientation | the roving axis | horizontal · vertical |
| toggle-group | data-disabled | the whole group is disabled (disables every item) | — |
| toggle-group-item | data-pressed | pressed (bare presence boolean - absent when off; the controller rederives the type-correct aria attribute from it) | — |
| toggle-group-item | data-disabled | the item (or the whole group) is disabled - the roving-focus collection filter | — |
| toggle-group-item | data-value | the item's key in the value set (always present, unique) | — |
| toggle-group-item | data-variant | cascaded from the root | default · outline |
| toggle-group-item | data-size | cascaded from the root | default · sm · lg |
| toggle-group-item | data-spacing | cascaded from the root - keys the segmented corner/border chain | — |
CSS variables
| Part | Variable | Description |
|---|---|---|
| toggle-group | --gap | the item gap, set inline from spacing: - the root's gap utility consumes it |
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 |
|---|---|---|
| root | poetry--core--toggle-group | registers · value type |
| root | poetry--core--roving-focus | registers · value orientation · value loop · keydown on keydown |
| item | poetry--core--toggle-group | toggle on click |