←
Tactile Neumorphic Dual-Dome Switch

Tactile Neumorphic Dual-Dome Switch

Photon Channel

Photorealistic 3D neumorphic toggle switch with an outer recessed bevel cavity, deep carved shadow trench, illuminated emerald photon channel, and dual-dome sculpted tactile thumb.

Interactive Playground & Studio

Live Controls
tactile-neumorphic-switch.tsx
Hover or click to interact
Customizer
PRESETS & VARIATIONS

All Variants & Themes

Click to test interactive spring physics on every theme

Off StatestateMode="off"

Resting neutral clay trench with dual-dome sculpted knob.

On StatestateMode="on"

Glowing emerald photon illumination channel with right dock.

Interactive Springtheme="emerald-photon"

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.

Features & Physics

✓Pixel-accurate OFF and ON state rendering
✓Dual-dome sculpted tactile thumb with spherical light highlights
✓Deep recessed shadow trench with illuminated emerald fill
✓Outer recessed bevel cavity with 3D inset lighting
✓60fps responsive spring motion physics

Code & Integration

Customized live with your active playground prop settings:

"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>
  );
}

Peer Dependencies

Requires Framer Motion and Tailwind CSS for spring physics and layout tokens:

npm install framer-motion clsx lucide-react tailwind-merge

API & Props Reference

PropTypeDefaultDescription
checkedbooleanfalseControlled boolean state.
stateMode"interactive" | "off" | "on""interactive"Force a static OFF, static ON, or live toggle.
theme"emerald-photon" | "cyber-cyan" | "magma-orange" | "dark-obsidian" | "hyper-violet" | "amber-crt""emerald-photon"Illumination theme palette.
size"sm" | "md" | "lg" | "xl""md"Proportional scale size preset.
showGridbooleantrueShows fine mesh canvas grid.