Toggle
Pressable toggle button with on/off state.
Installation
bin/rails g wabi:add toggle
bin/importmap pin @zag-js/toggle
bin/importmap pin @zag-js/vanillaPin @zag-js/toggle 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::Toggle.new { "Bold" }
Appearance
Pass appearance: :outline for a bordered variant. The default is borderless.
div(class: "flex items-center gap-2") do
render Components::UI::Toggle.new(appearance: :default) { "Default" }
render Components::UI::Toggle.new(appearance: :outline) { "Outline" }
end
Sizes
Use size: to pick :sm, :default, or :lg.
div(class: "flex items-center gap-2") do
render Components::UI::Toggle.new(appearance: :outline, size: :sm) { "Small" }
render Components::UI::Toggle.new(appearance: :outline, size: :default) { "Default" }
render Components::UI::Toggle.new(appearance: :outline, size: :lg) { "Large" }
end
Source
app/components/ui/toggle.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
# Pressable toggle button (Bold / Italic style). Diverges from Switch:
# Switch is a sliding control with a knob (settings-style); Toggle is a
# button that visually presses in/out (formatting-toolbar style).
class Toggle < Wabi::Base
variants do
base "inline-flex items-center justify-center rounded-md text-sm font-medium " \
"transition-colors motion-reduce:transition-none 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"
variant :appearance, {
default: "bg-transparent hover:bg-muted hover:text-muted-foreground",
outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
}, default: :default
variant :size, {
default: "h-10 px-3",
sm: "h-9 px-2.5",
lg: "h-11 px-5",
}, default: :default
end
def initialize(id: nil, name: nil, pressed: false, disabled: false, appearance: nil, size: nil, **attrs)
@id = id
@name = name
@pressed = pressed
@disabled = disabled
@appearance = appearance
@size = size
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
button(
**@attrs,
id: @id,
type: "button",
"data-state": @pressed ? "on" : "off",
data: {
controller: "wabi--toggle",
"wabi--toggle-pressed-value": @pressed.to_s,
"wabi--toggle-disabled-value": @disabled.to_s,
"wabi--toggle-name-value": @name,
},
class: merge_class(tokens(appearance: @appearance, size: @size), user_class),
&block
)
end
end
end
end
Accessibility
- role="button" with aria-pressed reflecting current state.
- Space and Enter toggle the state.
- Use Switch instead for binary settings; Toggle is for press-style buttons in toolbars.