Files
Htmx/docs/Components/Switch.md
T
2026-05-04 19:57:48 +05:00

6.6 KiB

Switch

A toggle switch (on/off). Renders as a hidden <input type="checkbox"> with a styled track and thumb driven by JavaScript. Fires no custom events — read the underlying checkbox value in form submissions.


HTML structure

label[for={id}].flex.items-center.gap-3.cursor-pointer
  div.switch-root.relative.w-11.h-6.rounded-full    ← outer track
    input[type=checkbox, id, name, class="sr-only", $$Checked$$]   ← hidden; holds true state
    div.switch-thumb.absolute.top-0.5.left-0.5...  ← animated thumb
  span.text-sm.select-none                          ← label text (omitted when empty)
    {label}

CSS mechanics

Class Effect
sr-only Hides the real checkbox visually but keeps it accessible
switch-root bg-input (off) / bg-primary (on) — toggled by JS adding switch-on class
switch-thumb h-5 w-5 rounded-full bg-background shadow transition-transform
translate-x-5 Added to thumb by JS when switch is on (slides right)

JavaScript (initSwitch in components.js)

Runs on DOMContentLoaded and htmx:afterSwap.

Per-switch initialization:

  1. Guard _switchInit prevents double-binding
  2. Sync visual state from the hidden checkbox checked property on load
  3. On label click: toggle checked, toggle switch-on on the track, toggle translate-x-5 on the thumb

Constructor signature

public Switch(
    string id,
    string label     = "",
    string name      = "",
    bool   isChecked = false)
Parameter Description
id Element id for the hidden checkbox; label's for attribute
label Optional visible text to the right of the toggle
name Form field name for the hidden checkbox
isChecked Initial on/off state

Usage examples

Basic on/off toggle

new Switch(
    id:    "notifications",
    label: "Enable notifications",
    name:  "enableNotifications",
    isChecked: true)

Toggle without label

new Switch(id: "darkMode", name: "darkMode")

Reading in a form handler

public record Command(
    [property: FromForm] string? EnableNotifications = null
);

bool notificationsOn = command.EnableNotifications != null;

Like all checkboxes, an unchecked switch is not included in the form submission. Use null as the default in your command record.

HTMX auto-save on change

// The hidden checkbox is named, so wrap in a form or use hx-include:
new Switch(
    id:    "maintenance",
    name:  "maintenanceMode",
    label: "Maintenance mode",
    isChecked: currentState)
<!-- Parent form with HTMX:
<form hx-post="/settings" hx-trigger="change from:#maintenance">
  $$MaintenanceSwitch$$
</form>
-->

Tips and tricks

  • The hidden checkbox carries the value "on" when checked (standard checkbox default). If you need "true", add value="true" by subclassing or via a wrapper form.
  • Because the click is handled on the <label> element, the switch works correctly even when the hidden input is not directly clicked.
  • For an HTMX auto-save switch, trigger on change from the hidden checkbox using hx-trigger="change from:#myId" on a parent element.
  • Pairing isChecked with a server-read preference ensures the switch reflects the saved state on every page load.
  • Pairing isChecked with a server-read preference ensures the switch reflects the saved state on every page load.

Complete page example

Templates/NotificationsPage.htmx

<div class="max-w-md mx-auto py-10">
  <h1 class="text-2xl font-bold mb-6">Notifications</h1>
  <form method="post" action="/notifications">
  $$AntiforgeryToken$$
  <div class="space-y-5 mb-8">
    $$EmailSwitch$$
    $$PushSwitch$$
    $$SmsSwitch$$
  </div>
  $$SaveBtn$$
  </form>
  $$SuccessAlert$$
</div>

Templates/NotificationsPage.htmx.cs

namespace Htmx.ApiDemo.Templates;

public sealed class NotificationsPage : NotificationsPageBase
{
  private readonly IHtmxComponent _email;
  private readonly IHtmxComponent _push;
  private readonly IHtmxComponent _sms;
  private readonly IHtmxComponent _save;
  private readonly IHtmxComponent _success;
  private readonly byte[] _afToken;

  public NotificationsPage(
    IAntiforgery af,
    HttpContext ctx,
    NotificationPrefs? prefs = null,
    bool saved = false)
  {
    var tokens = af.GetAndStoreTokens(ctx);
    _afToken = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".ToUtf8Bytes();

    _email   = new Components.Switch(id: "email-notif",  label: "Email notifications",  name: "emailNotif",  isChecked: prefs?.Email ?? true);
    _push    = new Components.Switch(id: "push-notif",   label: "Push notifications",   name: "pushNotif",   isChecked: prefs?.Push  ?? false);
    _sms     = new Components.Switch(id: "sms-notif",    label: "SMS notifications",    name: "smsNotif",    isChecked: prefs?.Sms   ?? false);
    _save    = new Components.Button("Save", type: "submit");
    _success = saved ? new Components.Alert(title: "Notification preferences saved.") : HtmxEmpty.Instance;
  }

  protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken);
  protected override void RenderEmailSwitch(HtmxRenderContext ctx)      => _email.Render(ctx.Next());
  protected override void RenderPushSwitch(HtmxRenderContext ctx)       => _push.Render(ctx.Next());
  protected override void RenderSmsSwitch(HtmxRenderContext ctx)        => _sms.Render(ctx.Next());
  protected override void RenderSaveBtn(HtmxRenderContext ctx)          => _save.Render(ctx.Next());
  protected override void RenderSuccessAlert(HtmxRenderContext ctx)     => _success.Render(ctx.Next());
}

POST handler

[Handler]
[MapPost("/notifications")]
public static partial class PostNotificationsHandler
{
  public record Command(
    [property: FromForm] string? EmailNotif = null,
    [property: FromForm] string? PushNotif  = null,
    [property: FromForm] string? SmsNotif   = null);

  private static Task<IResult> HandleAsync(
    [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct)
  {
    var prefs = new NotificationPrefs(
      Email: cmd.EmailNotif != null,
      Push:  cmd.PushNotif  != null,
      Sms:   cmd.SmsNotif   != null);
    // Persist prefs…
    return ctx.WriteHtmxPage(
      new NotificationsPage(af, ctx, prefs, saved: true), title: "Notifications");
  }
}

public record NotificationPrefs(bool Email, bool Push, bool Sms);

AppJsonSerializerContext.cs

[JsonSerializable(typeof(PostNotificationsHandler.Command), TypeInfoPropertyName = "NotificationsCommand")]