Toggle Group
Group of toggle buttons with single or multiple selection.
Installation
bin/rails g wabi:add toggle_group
bin/importmap pin @zag-js/toggle-group
bin/importmap pin @zag-js/vanillaPin @zag-js/toggle-group and @zag-js/vanilla at version 1.41+ using the +esm jsdelivr URLs — bin/importmap pin only downloads the main entry and leaves submodule imports unresolved.
Example
render Components::UI::ToggleGroup.new(type: :single, value: "center", name: "align") do
render Components::UI::ToggleGroupItem.new(value: "left") { "L" }
render Components::UI::ToggleGroupItem.new(value: "center") { "C" }
render Components::UI::ToggleGroupItem.new(value: "right") { "R" }
end
div(class: "h-4")
render Components::UI::ToggleGroup.new(type: :multiple, value: ["bold"], name: "format") do
render Components::UI::ToggleGroupItem.new(value: "bold") { "B" }
render Components::UI::ToggleGroupItem.new(value: "italic") { "I" }
render Components::UI::ToggleGroupItem.new(value: "underline") { "U" }
end
Source
app/components/ui/toggle_group.rb
# frozen_string_literal: true
require "json"
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class ToggleGroup < Wabi::Base
def initialize(type: :single, value: nil, name: nil, disabled: false, **attrs)
@type = type
@value = Array(value).compact
@name = name
@disabled = disabled
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
div(
**@attrs,
role: "group",
data: {
controller: "wabi--toggle-group",
"wabi--toggle-group-multiple-value": (@type == :multiple).to_s,
"wabi--toggle-group-value-value": @value.to_json,
"wabi--toggle-group-name-value": @name,
"wabi--toggle-group-disabled-value": @disabled.to_s,
},
class: merge_class("inline-flex items-center gap-1 rounded-md", user_class),
&block
)
end
end
end
end
app/components/ui/toggle_group_item.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class ToggleGroupItem < Wabi::Base
ITEM_CLASS = "inline-flex items-center justify-center rounded-md text-sm font-medium " \
"h-9 px-2.5 transition-colors motion-reduce:transition-none " \
"hover:bg-muted hover:text-muted-foreground " \
"focus-visible:outline-none focus-visible:ring-2 " \
"focus-visible:ring-ring focus-visible:ring-offset-2 " \
"disabled:pointer-events-none disabled:opacity-50 " \
"data-[state=on]:bg-accent data-[state=on]:text-accent-foreground"
def initialize(value:, disabled: false, **attrs)
@value = value
@disabled = disabled
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
button(
type: "button",
**@attrs,
"data-state": "off",
data: {
"wabi--toggle-group-target": "item",
"wabi-value": @value,
"wabi-disabled": @disabled.to_s,
},
class: merge_class(ITEM_CLASS, user_class)
) do
yield if block_given?
end
end
end
end
end
Accessibility
- role="group" wrapper + role="button" per item.
- Arrow keys move focus between items. Space toggles the focused item.
- type: :single enforces one selection; type: :multiple allows many.
- Hidden <input type="hidden"> emitted as `name` (single) or `name[]` (multiple) for form submission.