/YOGYAKARTA

8:29 AM

A custom toggle switch that doesn't suck

5 min reading

TL;DR

Native HTML checkboxes look cheap in modern apps. By pairing React with Framer Motion spring physics, you can build a dark-mode ready toggle switch with tactile micro-interactions that make your interface feel premium.

Ditching Native Controls

Let’s be real for a second. The default HTML checkbox input is ugly. It has always been ugly, and no amount of basic CSS overrides is ever going to make it look like it belongs in a sleek, high-end SaaS application. When you're trying to build a premium settings interface or a pricing billing toggle, using browser-default form controls completely ruins the visual polish of the product.

Micro-Interactions That Feel Expensive

When I’m building switches, I want that smooth, tactile slide effect that makes clicking it feel satisfying and physical. You know the vibe—the kind of micro-interaction where the toggle knob stretches slightly and snaps into place with perfect spring physics. To get that level of quality without writing massive CSS keyframe files, I build custom toggles using React and Framer Motion.

The Implementation

Here is my go-to implementation for a dark-mode ready, spring-animated toggle switch. It’s lightweight, fully accessible, handles state cleanly, and gives you that smooth, expensive-feeling bounce effect out of the box:

  <div
    className={`w-14 h-7 flex items-center rounded-full p-1 cursor-pointer transition-colors duration-300 ${
      isOn ? "bg-white" : "bg-zinc-800"
    }`}
    onClick={toggleSwitch}
    role="switch"
    aria-checked={isOn}
  >
    <motion.div
      className={`w-5 h-5 rounded-full shadow-md ${
        isOn ? "bg-black" : "bg-zinc-400"
      }`}
      layout
      transition={{
        type: "spring",
        stiffness: 600,
        damping: 30
      }}
      animate={{
        x: isOn ? 28 : 0
      }}
    />
  </div>
</div>
  <div
    className={`w-14 h-7 flex items-center rounded-full p-1 cursor-pointer transition-colors duration-300 ${
      isOn ? "bg-white" : "bg-zinc-800"
    }`}
    onClick={toggleSwitch}
    role="switch"
    aria-checked={isOn}
  >
    <motion.div
      className={`w-5 h-5 rounded-full shadow-md ${
        isOn ? "bg-black" : "bg-zinc-400"
      }`}
      layout
      transition={{
        type: "spring",
        stiffness: 600,
        damping: 30
      }}
      animate={{
        x: isOn ? 28 : 0
      }}
    />
  </div>
</div>
  <div
    className={`w-14 h-7 flex items-center rounded-full p-1 cursor-pointer transition-colors duration-300 ${
      isOn ? "bg-white" : "bg-zinc-800"
    }`}
    onClick={toggleSwitch}
    role="switch"
    aria-checked={isOn}
  >
    <motion.div
      className={`w-5 h-5 rounded-full shadow-md ${
        isOn ? "bg-black" : "bg-zinc-400"
      }`}
      layout
      transition={{
        type: "spring",
        stiffness: 600,
        damping: 30
      }}
      animate={{
        x: isOn ? 28 : 0
      }}
    />
  </div>
</div>

Pluto Grayson

Made in Framer

Created by

Create a free website with Framer, the website builder loved by startups, designers and agencies.