Toggle group
UI::ToggleGroupA row of buttons where one or several are chosen, posting like radios or checkboxes.
# VelvetKit
## Toggle group (`UI::ToggleGroup`)
A row of buttons where one or several are chosen, posting like radios or checkboxes.
Add it: `bin/rails generate velvetkit:add toggle_group`
Also adds: `icon`
### Arguments
```ruby
UI::ToggleGroup.new(name: nil, value: nil, multiple: false, variant: :default, size: :default, label: nil, form: nil, field: nil, required: false, disabled: false, class: nil, **html_attrs)
```
Slots: `with_item`
### Notes
A row of buttons where one is chosen (like radios) or several are
(`multiple: true`, like checkboxes). Each item is a real radio or checkbox
input inside its label, so it posts with the form under `name`, resets with
it, and needs no JavaScript: arrow keys move between radios and Space ticks a
checkbox. Bind it to a form builder with `form:` and `field:`.
A multiple group also posts an empty `name[]` value, so unticking every item
still sends the field, the way Rails' collection_check_boxes does.
`variant: :default` is a segmented control on a muted track; `:outline`
joins bordered buttons. Give the group a `label:` (its accessible name), and
give an icon-only item a `label:` too, which becomes its hidden text and
tooltip. Listen for `change` on the group to act as soon as the choice changes.
### Example: Outline
```erb
<%= render(UI::ToggleGroup.new(name: "billing", value: "monthly", variant: :outline, label: "Billing period")) do |group| %>
<% group.with_item(value: "monthly", label: "Monthly") %>
<% group.with_item(value: "yearly", label: "Yearly") %>
<% group.with_item(value: "lifetime", label: "Lifetime", disabled: true) %>
<% end %>
```
### Example: With Icons
```erb
<%= render(UI::ToggleGroup.new(name: "layout", value: "grid", label: "Layout")) do |group| %>
<% group.with_item(value: "list", label: "List", icon: "list") %>
<% group.with_item(value: "grid", label: "Grid", icon: "layout-grid") %>
<% group.with_item(value: "board", label: "Board", icon: "folder-kanban") %>
<% end %>
```
### Example: Icon Only
```erb
<%= render(UI::ToggleGroup.new(name: "align", value: "left", variant: :outline, label: "Text alignment")) do |group| %>
<% group.with_item(value: "left", label: "Align left", icon: "align-left", icon_only: true) %>
<% group.with_item(value: "center", label: "Align center", icon: "align-center", icon_only: true) %>
<% group.with_item(value: "right", label: "Align right", icon: "align-right", icon_only: true) %>
<% end %>
```
### Example: Multiple
```erb
<%= render(UI::ToggleGroup.new(name: "styles", value: %w[bold], multiple: true, variant: :outline, label: "Text style")) do |group| %>
<% group.with_item(value: "bold", label: "Bold", icon: "bold", icon_only: true) %>
<% group.with_item(value: "italic", label: "Italic", icon: "italic", icon_only: true) %>
<% group.with_item(value: "underline", label: "Underline", icon: "underline", icon_only: true) %>
<% end %>
```
### Example: Sizes
```erb
<div class="flex flex-col items-start gap-4">
<% %i[sm default lg].each do |size| %>
<%= render(UI::ToggleGroup.new(name: "size-#{size}", value: "b", size: size, label: "#{size} size")) do |group| %>
<% group.with_item(value: "a", label: "Draft") %>
<% group.with_item(value: "b", label: "Published") %>
<% group.with_item(value: "c", label: "Archived") %>
<% end %>
<% end %>
</div>
```
### Example: In a Form
```erb
<form class="flex max-w-sm flex-col gap-4" action="#" onsubmit="event.preventDefault()">
<div class="flex flex-col gap-2">
<span id="visibility-label" class="text-sm font-medium">Visibility</span>
<%= render(UI::ToggleGroup.new(name: "project[visibility]", value: "team", required: true, variant: :outline, class: "w-full", aria: { labelledby: "visibility-label" })) do |group| %>
<% group.with_item(value: "private", label: "Private", class: "flex-1") %>
<% group.with_item(value: "team", label: "Team", class: "flex-1") %>
<% group.with_item(value: "public", label: "Public", class: "flex-1") %>
<% end %>
</div>
<%= render(UI::Button.new(type: :submit, class: "self-start")) { "Save" } %>
</form>
```
bin/rails generate velvetkit:add toggle_group
- Also adds
- Icon
- Stimulus
- None, it's server-rendered
- Installs
- Nothing
Examples
Outline
outline.html.erb
<%= render(UI::ToggleGroup.new(name: "billing", value: "monthly", variant: :outline, label: "Billing period")) do |group| %>
<% group.with_item(value: "monthly", label: "Monthly") %>
<% group.with_item(value: "yearly", label: "Yearly") %>
<% group.with_item(value: "lifetime", label: "Lifetime", disabled: true) %>
<% end %>
With Icons
with_icons.html.erb
<%= render(UI::ToggleGroup.new(name: "layout", value: "grid", label: "Layout")) do |group| %>
<% group.with_item(value: "list", label: "List", icon: "list") %>
<% group.with_item(value: "grid", label: "Grid", icon: "layout-grid") %>
<% group.with_item(value: "board", label: "Board", icon: "folder-kanban") %>
<% end %>
Icon Only
icon_only.html.erb
<%= render(UI::ToggleGroup.new(name: "align", value: "left", variant: :outline, label: "Text alignment")) do |group| %>
<% group.with_item(value: "left", label: "Align left", icon: "align-left", icon_only: true) %>
<% group.with_item(value: "center", label: "Align center", icon: "align-center", icon_only: true) %>
<% group.with_item(value: "right", label: "Align right", icon: "align-right", icon_only: true) %>
<% end %>
Multiple
multiple.html.erb
<%= render(UI::ToggleGroup.new(name: "styles", value: %w[bold], multiple: true, variant: :outline, label: "Text style")) do |group| %>
<% group.with_item(value: "bold", label: "Bold", icon: "bold", icon_only: true) %>
<% group.with_item(value: "italic", label: "Italic", icon: "italic", icon_only: true) %>
<% group.with_item(value: "underline", label: "Underline", icon: "underline", icon_only: true) %>
<% end %>
Sizes
sizes.html.erb
<div class="flex flex-col items-start gap-4">
<% %i[sm default lg].each do |size| %>
<%= render(UI::ToggleGroup.new(name: "size-#{size}", value: "b", size: size, label: "#{size} size")) do |group| %>
<% group.with_item(value: "a", label: "Draft") %>
<% group.with_item(value: "b", label: "Published") %>
<% group.with_item(value: "c", label: "Archived") %>
<% end %>
<% end %>
</div>
In a Form
in_form.html.erb
<form class="flex max-w-sm flex-col gap-4" action="#" onsubmit="event.preventDefault()">
<div class="flex flex-col gap-2">
<span id="visibility-label" class="text-sm font-medium">Visibility</span>
<%= render(UI::ToggleGroup.new(name: "project[visibility]", value: "team", required: true, variant: :outline, class: "w-full", aria: { labelledby: "visibility-label" })) do |group| %>
<% group.with_item(value: "private", label: "Private", class: "flex-1") %>
<% group.with_item(value: "team", label: "Team", class: "flex-1") %>
<% group.with_item(value: "public", label: "Public", class: "flex-1") %>
<% end %>
</div>
<%= render(UI::Button.new(type: :submit, class: "self-start")) { "Save" } %>
</form>
API
Arguments
UI::ToggleGroup.new(name: nil, value: nil, multiple: false, variant: :default, size: :default, label: nil, form: nil, field: nil, required: false, disabled: false, class: nil, **html_attrs)
Slots:
with_item
Notes
A row of buttons where one is chosen (like radios) or several are
(`multiple: true`, like checkboxes). Each item is a real radio or checkbox
input inside its label, so it posts with the form under `name`, resets with
it, and needs no JavaScript: arrow keys move between radios and Space ticks a
checkbox. Bind it to a form builder with `form:` and `field:`.
A multiple group also posts an empty `name[]` value, so unticking every item
still sends the field, the way Rails' collection_check_boxes does.
`variant: :default` is a segmented control on a muted track; `:outline`
joins bordered buttons. Give the group a `label:` (its accessible name), and
give an icon-only item a `label:` too, which becomes its hidden text and
tooltip. Listen for `change` on the group to act as soon as the choice changes.