PhiaUi.Components.Switch (phia_ui v0.1.17)

Copy Markdown View Source

Accessible on/off switch component for Phoenix LiveView.

A two-state toggle rendered as a pill-shaped track with an animated sliding thumb. Uses role="switch" and aria-checked for full WAI-ARIA compliance. The animation is pure CSS (translate-x transition) — zero JavaScript required.

Sub-components

FunctionPurpose
switch/1Standalone switch button — manage state in your LiveView
form_switch/1Switch integrated with Phoenix.HTML.FormField and Ecto changesets

When to use

Use a switch for binary settings that take effect immediately or are submitted as part of a form. Switches communicate an on/off state more clearly than checkboxes for settings like "Enable notifications", "Public profile", or "Auto-renew subscription".

Use a checkbox/1 instead when the control is part of a list of options, or when the action requires explicit confirmation (e.g. "I agree to the terms").

Standalone usage

<%!-- Server-state switch: the LiveView owns the boolean --%>
<.switch checked={@notifications_enabled} phx-click="toggle_notifications" />

<%!-- Labelled by wrapping in a <label> --%>
<label class="flex items-center gap-3 cursor-pointer">
  <.switch checked={@dark_mode} phx-click="toggle_dark_mode" />
  <span class="text-sm font-medium">Dark mode</span>
</label>

<%!-- Disabled --%>
<.switch checked={true} disabled />

Form-integrated usage

form_switch/1 wraps a hidden checkbox so the value is submitted as part of the form. Clicking the visual switch toggles the hidden input without JS:

<.form for={@form} phx-submit="save_settings" phx-change="validate">
  <.form_switch field={@form[:email_notifications]} label="Email notifications" />
  <.form_switch field={@form[:sms_notifications]}   label="SMS notifications" />
  <.form_switch field={@form[:marketing_emails]}    label="Marketing emails" />
  <.button type="submit">Save settings</.button>
</.form>

Settings panel example

defmodule MyAppWeb.SettingsLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    changeset = Settings.change_notifications(socket.assigns.current_user)
    {:ok, assign(socket, form: to_form(changeset))}
  end

  def handle_event("save_settings", %{"settings" => params}, socket) do
    case Settings.update_notifications(socket.assigns.current_user, params) do
      {:ok, _settings} -> {:noreply, put_flash(socket, :info, "Settings saved")}
      {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))}
    end
  end
end

Accessibility

  • role="switch" identifies the element as a switch widget to screen readers
  • aria-checked="true" / "false" reflects the on/off state
  • disabled prevents interaction and sets aria-disabled via the native attribute
  • The focus ring (focus-visible:ring-2) ensures keyboard navigability
  • In form_switch/1, the label is associated with the hidden input via for/id

Summary

Functions

Renders a switch integrated with Phoenix.HTML.Form.

Renders a standalone accessible switch button.

Functions

form_switch(assigns)

Renders a switch integrated with Phoenix.HTML.Form.

Internally uses a hidden <input type="checkbox"> so the value is submitted as part of the form payload without JavaScript. The visual switch/1 component reads checked from the field value and reflects the state via CSS.

The <label> wraps both the hidden checkbox and the visual switch, making the entire row clickable to toggle the value. This is the standard PhiaUI pattern for form-bound switches.

The field value is normalised via truthy?/1 which handles true, "true", "on", 1, and "1" as truthy — matching all the ways a checkbox value may arrive from browser submissions or Ecto changesets.

Examples

<%!-- Settings form --%>
<.form for={@form} phx-submit="save">
  <.form_switch field={@form[:active]}         label="Account active" />
  <.form_switch field={@form[:email_opt_in]}   label="Email notifications" />
  <.form_switch field={@form[:two_factor_auth]} label="Two-factor authentication" />
  <.button type="submit">Save</.button>
</.form>

<%!-- Single inline setting --%>
<.form_switch field={@form[:public_profile]} label="Make profile public" />

<%!-- Without label --%>
<.form_switch field={@form[:enabled]} />

Attributes

  • field (Phoenix.HTML.FormField) (required) - A Phoenix.HTML.FormField struct obtained via @form[:field_name]. Provides the hidden input id and name, and the current value used to derive the checked state via truthy?/1.

  • label (:string) - Text rendered next to the switch inside a <label>. The label wraps both the hidden input and the visual switch, so clicking anywhere on the row (label text or switch) toggles the checkbox. When nil, only the switch is rendered.

    Defaults to nil.

  • class (:string) - Additional CSS classes applied to the <label> wrapper element. Defaults to nil.

switch(assigns)

Renders a standalone accessible switch button.

The switch is a <button type="button"> with role="switch" and aria-checked. Track colour transitions from bg-input (off) to bg-primary (on). The thumb slides with a translate-x CSS transition — no JavaScript.

Manage the state in your LiveView by passing :checked from your assigns and handling the phx-click event to toggle the boolean:

# In your LiveView:
def handle_event("toggle_notifications", _params, socket) do
  {:noreply, assign(socket, notifications: !socket.assigns.notifications)}
end

# In your template:
<.switch checked={@notifications} phx-click="toggle_notifications" />

Examples

<%!-- Basic off/on --%>
<.switch checked={false} phx-click="toggle" />
<.switch checked={true}  phx-click="toggle" />

<%!-- Disabled --%>
<.switch checked={false} disabled />
<.switch checked={true}  disabled />

<%!-- With phx-value for context --%>
<.switch
  checked={@feature_enabled}
  phx-click="toggle_feature"
  phx-value-feature="dark_mode"
/>

<%!-- Custom size via class --%>
<.switch checked={@enabled} phx-click="toggle" class="scale-90" />

Attributes

  • checked (:boolean) (required) - Whether the switch is in the on (checked) state. Controls the track colour and thumb position. Pass a boolean from your LiveView assigns and update it via phx-click.

  • disabled (:boolean) - When true, disables the switch. The button becomes non-interactive (pointer-events-none) and renders at 50% opacity to communicate its inactive state. The native disabled attribute is also set.

    Defaults to false.

  • class (:string) - Additional CSS classes applied to the outer <button> element via cn/1. Use to adjust sizing or spacing. Example: class="scale-125".

    Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the <button> element. Typically used for phx-click to toggle state in the LiveView: phx-click="toggle_feature" phx-value-feature="notifications" Supports all globals plus: ["phx-click", "phx-value", "name"].