Apple Switch

Animated toggle switch inspired by iOS, with draggable thumb, liquid press feedback, spring physics, size presets, and tone variants.

Installation

File Structure

apple-switch.tsx

Usage

"use client";

import { useState } from "react";
import { AppleSwitch } from "@/components/unlumen-ui/apple-switch";

export default function Example() {
  const [enabled, setEnabled] = useState(false);

  return (
    <AppleSwitch
      checked={enabled}
      onCheckedChange={setEnabled}
      label="Auto sync"
      description="Keep project data up to date."
    />
  );
}

API Reference

checked?
boolean

Controlled checked state.

defaultChecked?
boolean

Initial checked state for uncontrolled usage.

onCheckedChange?
(value: boolean) => void

Called when the switch is toggled.

label?
React.ReactNode

Text or node rendered beside the switch.

description?
React.ReactNode

Supporting text rendered below the label.

labelSide?
"left" | "right"
"right"

Which side of the switch the label content sits on.

size?
"sm" | "md" | "lg"
"md"

Switch size preset.

tone?
"neutral" | "accent"
"neutral"

Colour treatment for the checked state.

disabled?
boolean
false

Disables interaction.

className?
string

Additional classes applied to the switch root.

Notes

  • Built as a native <button role="switch"> — no Radix dependency. Pointer capture is used so drags work even when the pointer leaves the element.
  • The thumb supports click-to-toggle and drag-to-slide. A drag of more than 3 px suppresses the subsequent click event to avoid double-toggling.
  • Press feedback uses a "liquid" blur layer and a widening thumb driven by Motion springs (grabProgress). The effect collapses on pointer release.
  • Track fill, glow, and thumb travel are all derived from a single activeProgress transform, keeping the animation in sync without imperative sequencing.
  • Size presets store track and thumb dimensions separately, so the thumb naturally stretches wider than it is tall on press without altering the track.
  • The optional label wraps the switch in a <label> element linked via id/htmlFor, keeping accessibility correct without adding extra ARIA attributes.

Credits

Built by leo.

Keep in mind

Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.