Dual-Dome Neumorphic Switch • OFF & ONReact + Tailwind + Framer Motion

Tactile Neumorphic Dual-Dome Switch

Photorealistic skeuomorphic switch featuring an outer recessed bevel cavity, deep carved shadow trench, illuminated emerald photon channel, and dual-dome sculpted tactile thumb.

preview.tsx
Interactions:0
Hover or click to interact

Customizer

ALL VARIANTS & PRESETS

All Variants & States

Click or hover over switches to test spring physics & photorealistic lighting

Off Statestate="off"
Resting neutral clay trench with dual-dome sculpted knob
On Statestate="on"
Glowing emerald photon illumination channel with right dock
Interactive Springmode="live"
Interactive 60fps spring toggle with dynamic channel fill
Cyber Laser Cyantheme="cyber-cyan"
High-voltage laser cyan glowing optical underlayer
UI Pirate Magmatheme="magma-orange"
Signature magma orange molten photon illumination
Dark Obsidian Stealththeme="dark-obsidian"
Midnight dark clay chassis with glowing sapphire channel

Installation & Setup

Install the required dependencies for Framer Motion spring animations and utility classes:

npm install framer-motion clsx

Code & Integration

TactileNeumorphicSwitch.tsx• Production Ready
"use client";

import React, { useState } from "react";
import { motion } from "framer-motion";

export interface TactileNeumorphicSwitchProps {
  checked?: boolean;
  defaultChecked?: boolean;
  onChange?: (checked: boolean) => void;
  theme?: "emerald-photon" | "cyber-cyan" | "magma-orange" | "dark-obsidian" | "hyper-violet" | "amber-crt";
  size?: "sm" | "md" | "lg" | "xl";
  disabled?: boolean;
  label?: string;
  showGrid?: boolean;
}

export function TactileNeumorphicSwitch({
  checked: controlledChecked,
  defaultChecked = false,
  onChange,
  theme = "emerald-photon",
  size = "md",
  disabled = false,
  label,
  showGrid = true,
}: TactileNeumorphicSwitchProps) {
  const [internalChecked, setInternalChecked] = useState(defaultChecked);
  const isChecked = controlledChecked !== undefined ? controlledChecked : internalChecked;

  const handleToggle = () => {
    if (disabled) return;
    const next = !isChecked;
    if (controlledChecked === undefined) setInternalChecked(next);
    onChange?.(next);
  };

  return (
    <div
      role="switch"
      aria-checked={isChecked}
      tabIndex={disabled ? -1 : 0}
      onClick={handleToggle}
      className="relative w-[320px] h-[140px] rounded-[70px] bg-[#DFE3EB] shadow-[inset_5px_6px_12px_rgba(150,162,182,0.65),inset_-5px_-6px_12px_rgba(255,255,255,0.95)] flex items-center justify-center cursor-pointer select-none"
    >
      {/* Deep Carved Shadow Trench */}
      <div className="relative w-[276px] h-[84px] rounded-[42px] bg-gradient-to-b from-[#C2C9D6] via-[#D4DBE6] to-[#E7ECF3] shadow-[inset_0_8px_12px_rgba(60,72,92,0.45),inset_0_-2px_4px_rgba(255,255,255,0.85)] overflow-hidden flex items-center">
        {/* Emerald Illuminated Active Fill */}
        <motion.div
          animate={{
            width: isChecked ? "256px" : "0px",
            opacity: isChecked ? 1 : 0,
          }}
          transition={{ type: "spring", stiffness: 380, damping: 26 }}
          className="absolute left-0 top-0 bottom-0 bg-gradient-to-r from-[#02B86E] via-[#0AD483] to-[#2BF3A4] shadow-[inset_0_7px_10px_rgba(0,85,48,0.55),0_0_18px_rgba(16,229,153,0.35)]"
        />
      </div>

      {/* Dual-Dome Sculpted Thumb */}
      <motion.div
        animate={{ x: isChecked ? 70 : -70 }}
        transition={{ type: "spring", stiffness: 380, damping: 26 }}
        className="absolute z-20 w-[136px] h-[96px] rounded-[48px] bg-gradient-to-br from-white via-[#F5F7FA] to-[#DFE4EC] shadow-[0_16px_28px_-3px_rgba(35,48,70,0.32),0_8px_14px_-2px_rgba(35,48,70,0.22),inset_0_2px_3px_white,inset_0_-3px_4px_rgba(150,162,180,0.35)] flex items-center justify-between px-2"
      >
        <div className="w-[60px] h-[60px] rounded-full bg-[radial-gradient(circle_at_35%_32%,white_0%,rgba(255,255,255,0.75)_35%,rgba(185,195,212,0.7)_100%)] shadow-[inset_0_2px_3px_rgba(255,255,255,0.95),inset_0_-2px_3px_rgba(140,152,172,0.4)]" />
        <div className="w-[2px] h-[55%] rounded-full bg-gradient-to-b from-white via-slate-400 to-white opacity-35" />
        <div className="w-[60px] h-[60px] rounded-full bg-[radial-gradient(circle_at_35%_32%,white_0%,rgba(255,255,255,0.75)_35%,rgba(185,195,212,0.7)_100%)] shadow-[inset_0_2px_3px_rgba(255,255,255,0.95),inset_0_-2px_3px_rgba(140,152,172,0.4)]" />
      </motion.div>
    </div>
  );
}

Component API Reference

PropTypeDefaultDescription
checkedbooleanundefinedControlled boolean state of the switch (true = ON, false = OFF)
defaultCheckedbooleanfalseInitial boolean state when uncontrolled
stateMode"interactive" | "off" | "on""interactive"Force a static OFF or ON state, or interactive toggle
themeTactileSwitchTheme"emerald-photon"Color scheme for illuminated channel and shadow aesthetics
size"sm" | "md" | "lg" | "xl""md"Proportional scaling dimension preset
onChange(checked: boolean) => voidundefinedCallback fired whenever the switch is toggled
showGridbooleantrueRenders the fine mesh canvas grid behind the switch

Personalized Help

Struggling with skeuomorphic switches, neumorphic UI, or custom React toggles? Let's talk about your product.

UI Pirate is a product design & development agency trusted by 50+ SaaS founders and enterprise teams across the US, UK & beyond. Tell us what you need.