# Toggle Spring Switch — React Toggle Switch

> A switch whose thumb stretches toward the other side while pressed, then snaps across with a little overshoot on release.

- Category: [Toggles & Switches](https://pixelpapa.com/components/toggles.md)
- Tags: toggle, switch, spring, press, stretch
- Dependencies: motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/toggles/toggle-spring-switch

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/toggle-spring-switch.json"
```

The file is written to `components/pixelpapa/toggle-spring-switch.tsx`. To install manually, run `npm install motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { motion } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";

type SpringSwitchProps = {
  /** Accessible name. */
  label: string;
  checked?: boolean;
  defaultChecked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
  className?: string;
};

const sizes = {
  sm: { w: 36, h: 22, thumb: 16, stretch: 5 },
  md: { w: 48, h: 28, thumb: 22, stretch: 7 },
  lg: { w: 64, h: 36, thumb: 28, stretch: 10 },
};

export function SpringSwitch({
  label,
  checked,
  defaultChecked = false,
  onCheckedChange,
  size = "md",
  disabled = false,
  className,
}: SpringSwitchProps) {
  const [internal, setInternal] = useState(defaultChecked);
  const [pressed, setPressed] = useState(false);
  const on = checked ?? internal;
  const s = sizes[size];
  const pad = (s.h - s.thumb) / 2;
  const travel = s.w - s.thumb - pad * 2;
  const grow = pressed ? s.stretch : 0;

  function toggle() {
    setInternal(!on);
    onCheckedChange?.(!on);
  }

  return (
    <button
      type="button"
      role="switch"
      aria-checked={on}
      aria-label={label}
      disabled={disabled}
      onClick={toggle}
      onPointerDown={() => setPressed(true)}
      onPointerUp={() => setPressed(false)}
      onPointerLeave={() => setPressed(false)}
      onPointerCancel={() => setPressed(false)}
      onKeyDown={(e) => (e.key === " " || e.key === "Enter") && setPressed(true)}
      onKeyUp={() => setPressed(false)}
      onBlur={() => setPressed(false)}
      style={{ width: s.w, height: s.h }}
      className={cn(
        "relative shrink-0 rounded-full transition-colors duration-300 disabled:cursor-not-allowed disabled:opacity-50 [-webkit-tap-highlight-color:transparent]",
        on ? "bg-primary" : "bg-input",
        className,
      )}
    >
      {/* When on, the thumb grows leftwards, so x backs off by the same amount. */}
      <motion.span
        aria-hidden
        initial={false}
        animate={{ x: on ? travel - grow : 0, width: s.thumb + grow }}
        transition={{ type: "spring", stiffness: 520, damping: pressed ? 34 : 22, mass: 0.7 }}
        style={{ top: pad, left: pad, height: s.thumb }}
        className="absolute block rounded-full bg-white shadow-[0_1px_3px_rgb(0_0_0/0.25),0_0_0_0.5px_rgb(0_0_0/0.06)]"
      />
    </button>
  );
}

const rows = [
  { size: "sm", title: "Compact", note: "For dense tables" },
  { size: "md", title: "Default", note: "Forms and settings" },
  { size: "lg", title: "Large", note: "Touch surfaces" },
] as const;

export default function ToggleSpringSwitchDemo() {
  const [values, setValues] = useState([true, false, true]);

  return (
    <div className="flex flex-col items-center gap-7">
      <div className="flex items-end gap-10">
        {rows.map((row, i) => (
          <div key={row.size} className="flex flex-col items-center gap-3">
            <SpringSwitch
              size={row.size}
              label={`${row.title} switch`}
              checked={values[i]}
              onCheckedChange={(v) => setValues((prev) => prev.map((p, j) => (j === i ? v : p)))}
            />
            <div className="text-center">
              <p className="text-sm font-medium text-foreground">{row.title}</p>
              <p className="text-xs text-muted-foreground">{row.note}</p>
            </div>
          </div>
        ))}
      </div>
      <p className="text-xs text-muted-foreground">Press and hold to feel the thumb stretch before it flips.</p>
    </div>
  );
}
```

## AI prompt

```text
Add the "Toggle Spring Switch" component from PixelPapa UI to this project.

What it is: A switch whose thumb stretches toward the other side while pressed, then snaps across with a little overshoot on release.
Category: Toggles & Switches. Depends on: motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/toggle-spring-switch.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/toggle-spring-switch.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/toggle-spring-switch" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More toggles & switches

- [Toggle Day Night](https://pixelpapa.com/components/toggles/toggle-day-night.md) (Pro): A theme switch drawn as a tiny sky: the sun rolls across and becomes a cratered moon, clouds sink away and stars pop out one by one.
- [Toggle Icon Morph](https://pixelpapa.com/components/toggles/toggle-icon-morph.md): A switch with a glyph inside the thumb: the two strokes of the cross swing and re-join into a check as the thumb rolls across.
- [Toggle iOS With Label List](https://pixelpapa.com/components/toggles/toggle-ios-with-label-list.md) (Pro): A settings card of labelled switches under a master toggle; turning the master off folds the dependent rows away with a staggered collapse.
- [Toggle Like Bookmark](https://pixelpapa.com/components/toggles/toggle-like-bookmark.md): Icon toggles that each fill in their own way: the heart pops, the bookmark drops in from above, the star spins and the bell rings, all with a ring burst.
- [Toggle Lock Shake](https://pixelpapa.com/components/toggles/toggle-lock-shake.md) (Pro): A switch that can be locked: clicking it then shakes its head, rattles the padlock in the thumb and pops up the reason it cannot change.
- [Toggle Segmented Theme](https://pixelpapa.com/components/toggles/toggle-segmented-theme.md) (Pro): A light / dark / system picker with a sliding thumb and icons that react when chosen, paired with a miniature window that wipes between themes and splits in two for "system".
