All components
uiJSX

Wooden Switch

A high-fidelity skeuomorphic toggle featuring procedural audio and organic textures.

Motion
JSX
"use client";

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

class="code-comment">// --- 1. PROCEDURAL AUDIO ENGINE ---
const playWoodSound = (type = class="code-string">'click') => {
  if (typeof window === class="code-string">'undefined') return;

  const AudioContext = window.AudioContext || window.webkitAudioContext;
  if (!AudioContext) return;

  const ctx = new AudioContext();
  const t = ctx.currentTime;

  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  const filter = ctx.createBiquadFilter();

  const startFreq = type === class="code-string">'on' ? 350 : 300; 
  const endFreq = 50;
  const duration = 0.12;

  osc.type = "sine"; 
  osc.frequency.setValueAtTime(startFreq, t);
  osc.frequency.exponentialRampToValueAtTime(endFreq, t + duration);

  filter.type = "lowpass";
  filter.frequency.setValueAtTime(800, t);

  gain.gain.setValueAtTime(0, t);
  gain.gain.linearRampToValueAtTime(1, t + 0.01);
  gain.gain.exponentialRampToValueAtTime(0.001, t + duration);

  osc.connect(filter);
  filter.connect(gain);
  gain.connect(ctx.destination);

  osc.start(t);
  osc.stop(t + duration);
};

class="code-comment">// --- 2. THE COMPONENT ---
const WoodenToggle = () => {
  const [isOn, setIsOn] = useState(false);
  const containerRef = useRef(null);

  const toggleSwitch = () => {
    const newState = !isOn;
    setIsOn(newState);
    playWoodSound(newState ? class="code-string">'on' : class="code-string">'off');
  };

  return (
    <main className="min-h-screen flex flex-col items-center justify-center relative overflow-hidden bg-[#e0e5ec] px-4">
      
      {class="code-comment">/* --- BACKGROUND ATMOSPHERE --- */}
      <div className="absolute inset-0 z-0">
        <div className="absolute inset-0 bg-gradient-to-br from-gray-200 to-gray-400"></div>
        <div className="absolute top-[-20%] left-[-20%] w-[80%] h-[80%] bg-blue-300/20 rounded-full blur-[120px]"></div>
        <div className="absolute bottom-[-20%] right-[-20%] w-[80%] h-[80%] bg-orange-300/20 rounded-full blur-[120px]"></div>
        <div className="absolute inset-0 opacity-40 mix-blend-overlay bg-[url(class="code-string">'https:class="code-comment">//grainy-gradients.vercel.app/noise.svg')]"></div>
      </div>
      {class="code-comment">/* --- HOUSING CONTAINER (Responsive) --- */}
      <div 
        className="relative z-10 w-full max-w-[320px] sm:max-w-[500px] h-[100px] sm:h-[160px] rounded-full p-2 sm:p-4 flex items-center overflow-hidden cursor-pointer active:scale-[0.99] transition-transform duration-200"
        style={{
          backgroundColor: class="code-string">'#e0e5ec',
          boxShadow: class="code-string">`
            20px 20px 60px #bec3c9, 
            -20px -20px 60px #ffffff
          `
        }}
        onClick={toggleSwitch}
        ref={containerRef}
      >
        {class="code-comment">/* Subtle Stone Texture */}
        <div className="absolute inset-0 opacity-10 bg-[url(class="code-string">'https:class="code-comment">//grainy-gradients.vercel.app/noise.svg')] mix-blend-multiply pointer-events-none"></div>

        {class="code-comment">/* --- THE TRACK (DEBOSSED) --- */}
        <div className="relative w-full h-full rounded-full bg-[#d1d9e6] overflow-hidden transition-colors duration-500 shadow-inner"
          style={{
            boxShadow: class="code-string">`
              inset 10px 10px 20px #b1b9c4, 
              inset -10px -10px 20px #ffffff
            `
          }}
        >
          {class="code-comment">/* Background Texture for Track */}
          <div className="absolute inset-0 opacity-10 bg-[url(class="code-string">'https:class="code-comment">//grainy-gradients.vercel.app/noise.svg')] mix-blend-multiply pointer-events-none"></div>

          {class="code-comment">/* Track Labels (Responsive Text Size) */}
          <div className="absolute inset-0 flex items-center justify-between px-8 sm:px-16 pointer-events-none select-none z-0">
             {class="code-comment">/* LEFT LABEL */}
             <span className={class="code-string">`text-lg sm:text-2xl font-black tracking-widest transition-all duration-500 ease-out
               ${!isOn ? 'text-neutral-400/60 scale-100' : 'text-neutral-400/10 scale-95'}`}
               style={{ textShadow: class="code-string">'1px 1px 0px rgba(255,255,255,0.7)' }}
             >
               PUBLIC
             </span>

             {class="code-comment">/* RIGHT LABEL */}
             <span className={class="code-string">`text-lg sm:text-2xl font-black tracking-widest transition-all duration-500 ease-out
               ${isOn ? 'text-neutral-400/60 scale-100' : 'text-neutral-400/10 scale-95'}`}
               style={{ textShadow: class="code-string">'1px 1px 0px rgba(255,255,255,0.7)' }}
             >
               PRIVATE
             </span>
          </div>

          {class="code-comment">/* --- THE WOODEN SLIDER --- */}
          <motion.div
            className="absolute top-1.5 bottom-1.5 sm:top-2 sm:bottom-2 w-[50%] rounded-full z-10 flex items-center justify-center shadow-2xl"
            initial={false}
            animate={{
              x: isOn ? "96%" : "4%", 
            }}
            transition={{
              type: "spring",
              stiffness: 110, 
              damping: 22,    
              mass: 1.3
            }}
          >
             {class="code-comment">/* 1. WOOD TEXTURE */}
             <div 
               className="absolute inset-0 rounded-full overflow-hidden"
               style={{
                 backgroundImage: class="code-string">`url('https:class="code-comment">//images.unsplash.com/photo-1632199495802-18f7d21f323b?q=80&w=987&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')`,
                 backgroundSize: class="code-string">'150%',
                 backgroundPosition: class="code-string">'center',
                 filter: class="code-string">'saturate(1.1) contrast(1.1) brightness(1)'
               }}
             >
                <div className="absolute inset-0 opacity-20 mix-blend-multiply bg-[url(class="code-string">'https:class="code-comment">//grainy-gradients.vercel.app/noise.svg')]"></div>
             </div>

             {class="code-comment">/* 2. LIGHTING */}
             <div className="absolute inset-0 rounded-full pointer-events-none"
               style={{
                 boxShadow: class="code-string">`
                   inset 0px 1px 2px rgba(255,255,255,0.6), 
                   inset 0px -2px 4px rgba(0,0,0,0.4),     
                   0 10px 15px -3px rgba(0,0,0,0.5),
                   0 4px 6px -2px rgba(0,0,0,0.1)
                 `
               }}
             />
             
             {class="code-comment">/* Glossy Reflection */}
             <div className="absolute top-2 sm:top-3 left-8 sm:left-12 right-8 sm:right-12 h-[30%] bg-gradient-to-b from-white/25 to-transparent rounded-full blur-[3px] pointer-events-none"></div>

             {class="code-comment">/* 3. ACTIVE TEXT (Engraved Look) */}
             <div className="relative z-20 overflow-hidden mix-blend-overlay">
               <AnimatePresence mode="wait" initial={false}>
                 <motion.span
                   key={isOn ? "private" : "public"}
                   initial={{ opacity: 0, scale: 0.95 }}
                   animate={{ opacity: 1, scale: 1 }}
                   exit={{ opacity: 0, scale: 0.95 }}
                   transition={{ duration: 0.2 }}
                   className="block text-lg sm:text-xl font-black tracking-widest text-[#3d2312]"
                   style={{
                     textShadow: class="code-string">`
                       0px 1px 1px rgba(255,255,255,0.5), 
                       0px -1px 1px rgba(0,0,0,0.8)
                     `
                   }}
                 >
                   {isOn ? "PRIVATE" : "PUBLIC"}
                 </motion.span>
               </AnimatePresence>
             </div>

          </motion.div>
        </div>

      </div>

      {class="code-comment">/* --- FOOTER TEXT --- */}
      <div className="relative z-10 mt-12 sm:mt-16">
        <span className="text-[10px] sm:text-xs font-mono uppercase tracking-[0.2em] text-neutral-400 select-none">
          Click or Tap to Toggle
        </span>
      </div>

    </main>
  );
};

export default WoodenToggle;

Live Preview

Need a custom component built?

I build reusable, production-ready components for Web3 and AI projects.

Book a call