Buttons & Toggles

Switch

A spring-driven React switch whose thumb can be dragged to toggle, with optional crossfading icons inside the thumb, a pressed-state toggle-button variant and native form submission.

The switch every settings page needs. Click it, press Space / Enter, or grab the thumb and drag it across; the thumb moves on a spring, the track colour follows the drag progress, and on release it snaps to the far side if you crossed the halfway point or springs back otherwise. You can put on/off icons inside the thumb, which rotate and fade into each other when toggling. variant="button" turns it into a pressed-state toggle button (aria-pressed), suited to "pressed = on" cases like a text toolbar. State supports controlled and uncontrolled modes, and passing name submits it with a native form.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add switch.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

500
30
Active edited usageDefault props
<Switch/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/switch.en.json

Or, once registries are configured in components.json, install it as @webberui/switch.

Usage

import { Moon, Sun } from "lucide-react";
import { Switch } from "@/components/ui/switch";

const [dark, setDark] = React.useState(false);

<Switch
  label="Dark mode"
  description="Switch the interface colours to match your preference"
  checked={dark}
  onCheckedChange={setDark}
  icons={{ on: <Moon />, off: <Sun /> }}
/>

// Pressed-state toggle button
<Switch variant="button" label="Bold" defaultChecked />

// Native form: submits newsletter=yes when on
<Switch name="newsletter" value="yes" label="Subscribe to the newsletter" />

Props

PropTypeDefaultDescription
checkedboolean—Controlled checked state; when omitted the component manages it internally (uncontrolled)
defaultCheckedbooleanfalseInitial state in uncontrolled mode
onCheckedChange(checked: boolean) => void—Called when the state changes, with the new state
size"sm" | "md" | "lg""md"Size: the track is 28×16, 36×20 or 44×24
disabledbooleanfalseDisabled: cannot be clicked, dragged or toggled from the keyboard, and is greyed out
labelReactNode—Label; rendered as a clickable label in the switch variant and as the button text in the button variant
descriptionReactNode—Supporting description, linked via aria-describedby
variant"switch" | "button""switch"Visual variant: sliding switch or pressed-state toggle button
iconsSwitchIcons—On/off icons inside the thumb, crossfaded when toggling
stiffnessnumber500Thumb spring stiffness
dampingnumber30Thumb spring damping
namestring—Native form field name; when set, a hidden input is submitted with the form
valuestring"on"Value submitted with the form when on
classNamestring—Passed through to the outermost container

SwitchIcons

FieldTypeDefaultDescription
onReactNode—Icon shown when on
offReactNode—Icon shown when off

How it works

  • Dragging: pressing on the thumb locks the pointer with setPointerCapture; the thumb follows the pointer, clamped to the track. On release it snaps to the far side if it crossed the halfway point, otherwise it springs back. Movement under 3px counts as a click, and the click the browser fires after a drag is swallowed so the switch does not toggle twice.
  • The track's on-colour is a layer over the base colour: its opacity follows the drag progress 0–1, and otherwise transitions over --wb-switch-duration (150ms by default). Override the variable globally or on a parent to change the pacing.
  • In dark mode the on-track is light, so the thumb turns dark to keep contrast.
  • The switch is not a text input; Space / Enter are handled by the native <button>, so IME composition never interferes.
  • Form submission mirrors a native checkbox: name=value is only sent when on, and nothing is sent when off.

Accessibility

  • The switch variant is a <button role="switch"> with aria-checked; label is linked via htmlFor, so clicking the text toggles it too
  • The button variant is a <button aria-pressed> (not role="switch"), suited to toolbars; in this variant description is provided to screen readers as visually hidden text
  • description is linked via aria-describedby; the thumb and icons are hidden from assistive technology
  • Keyboard: Tab to focus, then Space or Enter toggles; a focus-visible ring shows on focus; when disabled it cannot be focused or operated
  • When the user has reduce motion enabled, the thumb jumps into place instead of springing, dragging is disabled (click only), icons only fade without rotating, and the toggle button no longer scales when held