PetalComponents.ToggleGroup (petal_components v4.11.3)

Copy Markdown View Source

Summary

Functions

A segmented selection control: one rail of options where the pressed state is the point. Where button_group groups actions (press one, something happens, nothing stays lit), toggle_group holds a selection - exactly one option (default) or any number of them (multiple).

Functions

toggle_group(assigns)

A segmented selection control: one rail of options where the pressed state is the point. Where button_group groups actions (press one, something happens, nothing stays lit), toggle_group holds a selection - exactly one option (default) or any number of them (multiple).

The component is stateless and server-driven, the LiveView way: you pass the current value, every press sends on_change with the pressed option in phx-value-toggle, and you assign the value back. No hook, no client state.

Single select renders native radio inputs (the same mechanics as the colour-scheme switch's segmented variant): real radiogroup semantics, one tab stop, and arrow keys move the selection - the full WAI-ARIA radio pattern with zero JavaScript. Multiple select renders aria-pressed toggle buttons, the toolbar pattern.

Single select - a density rail

<.toggle_group aria_label="Density" value={@density} on_change="set_density">
  <:item value="compact">Compact</:item>
  <:item value="cozy">Cozy</:item>
  <:item value="comfortable">Comfortable</:item>
</.toggle_group>

def handle_event("set_density", %{"toggle" => density}, socket) do
  {:noreply, assign(socket, density: density)}
end

Multiple select - formatting toggles

With multiple, value is a list and the server owns the toggle logic:

<.toggle_group multiple aria_label="Formatting" value={@formats} on_change="toggle_format">
  <:item value="bold" aria-label="Bold"><.icon name="hero-bold" /></:item>
  <:item value="italic" aria-label="Italic"><.icon name="hero-italic" /></:item>
</.toggle_group>

def handle_event("toggle_format", %{"toggle" => format}, socket) do
  formats = socket.assigns.formats

  formats =
    if format in formats, do: List.delete(formats, format), else: [format | formats]

  {:noreply, assign(socket, formats: formats)}
end

Variants

variant="solid" (default) is the wash rail with a neutral chip - the segmented-control look the scheme switch uses. variant="outline" is the bordered toolbar rail with floating chips, at home next to a button_group. variant="accent" keeps the wash rail but paints the selection in the brand accent, for a committed setting rather than a view preference.

Values survive the phx-value-* string round-trip: pressed comparison is string-based, so value={2} still highlights after the server re-assigns the "2" it received.

The single-select radios detach themselves from any surrounding form (their form attribute points at nothing), so dropping a toggle group inside a <.form> never posts a stray "<id>-toggle" param.

Attributes

  • id (:string) - Defaults to nil.
  • aria_label (:string) (required) - the ARIA label for the group.
  • value (:any) - the selected value - a single term, or a list when multiple. Defaults to nil.
  • on_change (:any) - event name (or JS command) sent on press; the pressed option arrives in phx-value-toggle. Omit it and put phx-click on individual items instead. Defaults to nil.
  • multiple (:boolean) - treat value as a list; any number of options can be pressed. Defaults to false.
  • variant (:string) - solid is the wash rail with a neutral chip; outline is the bordered toolbar rail; accent paints the selection in the brand colour. Defaults to "solid". Must be one of "solid", "outline", or "accent".
  • size (:string) - Defaults to "md". Must be one of "sm", "md", or "lg".
  • disabled (:boolean) - disables every item. Defaults to false.
  • class (:any) - extra classes for the rail. Defaults to nil.
  • Global attributes are accepted.

Slots

  • item (required) - Accepts attributes:
    • value (:any) (required) - the option this item represents.
    • disabled (:boolean) - disables this item only.
    • class (:any) - extra classes for this item.
    • aria-label (:string) - accessible name for icon-only items - reaches the radio/button itself. Also marks the item as icon-only, which squares its chip (don't set it on items with visible text).