15 / 47
All 47 in 47 Tailwind CSS Toggles & Switches
- 01Tailwind iOS-Style Toggle Switch with appearance-none
- 02Tailwind Dark Mode Sun / Moon Theme Switcher
- 03Tailwind Neumorphic Soft-UI Pill Toggle
- 04Tailwind Gooey Liquid Morphing Switch with SVG Filter
- 05Tailwind Bouncy Elastic Physics Toggle (Overshoot Cubic-Bezier)
- 06Tailwind ON / OFF Text Switch with Labels Inside the Track
- 07Tailwind SaaS Pricing Toggle — Monthly / Annually Segmented Pill
- 08Tailwind Mechanical Keycap Toggles with Haptic Feedback
- 09Tailwind Waveform Mute Toggle with Animated EQ Bars
- 10Tailwind Timed Auto-Off Toggle with Conic Countdown Ring
- 11Tailwind Press-and-Hold to Confirm Toggle (Fingerprint Scan)
- 12Tailwind Split-Flap Departure Board Toggle (3D Flip)
- 13Tailwind Permission Toggle Group with :has() Live Status Badge
- 14Tailwind Material Design 3 Switch with State Layer
- 15Tailwind Skeuomorphic Wall Light Switch with Room Relight
- 16Tailwind Neon Cyberpunk Toggle with Clipped Corners
- 17Tailwind Brutalist Snap Toggle with Hard Offset Shadow
- 18Tailwind Day / Night Landscape Scene Switch
- 19Tailwind 3D Flip Card Toggle (rotateY Switch)
- 20Tailwind Sliding Pill Toggle with Label Inside the Thumb
- 21Tailwind Glassmorphism Frosted Toggle with backdrop-blur
- 22Tailwind Rotary Knob Toggle with Brushed-Metal Conic Gradient
- 23Tailwind iOS Settings List with Grouped Toggle Rows
- 24Tailwind Minimal Outline Toggle Drawn in currentColor
- 25Tailwind Squash-and-Stretch Pebble Toggle
- 26Tailwind Industrial Rocker I/O Power Switch
- 27Tailwind Accessible role=switch Toggle with forced-colors Support
- 28Tailwind Macro-Pad Toggle Grid with RGB Underglow
- 29Tailwind Radial VU Meter Mute Toggle
- 30Tailwind Segmented Pip Countdown Auto-Off Toggle
- 31Tailwind Slide-to-Confirm Toggle Rail (Drag or Arrow Keys)
- 32Tailwind Odometer Roll ON / OFF Toggle
- 33Tailwind Master Toggle with Indeterminate Tri-State
- 34Tailwind iOS Control Centre Tile Toggles
- 35Tailwind Material 3 Connected Icon Toggle Buttons
- 36Tailwind Two-Gang Wall Plate with Cumulative Room Light
- 37Tailwind Neon Toggle with Animated Conic Border Light
- 38Tailwind Brutalist Rubber-Stamp Toggle
- 39Tailwind Aurora Night Sky Toggle Switch
- 40Tailwind 3D Rotating Cube Toggle
- 41Tailwind Three-Position Off / Auto / On Selector
- 42Tailwind Liquid Glass Toggle with Specular Highlight
- 43Tailwind Three-Detent Rotary Selector Knob
- 44Tailwind Hardware Slide Switch with Knurled Grip
- 45Tailwind Battery Saver Toggle with Live Fill Meter
- 46Tailwind Morphing Sun-to-Moon Icon Toggle (SVG Stroke)
- 47Tailwind Guarded Kill Switch Under a Flip-Up Safety Cover
Tailwind Skeuomorphic Wall Light Switch with Room Relight
A wall-plate rocker with a bevelled plate, slotted screws and a paddle that tips in real perspective — and when you flip it the whole room warms from dusk to lamp-light, because the section watches the switch with has-checked:.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="ttg-15 relative overflow-hidden w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg15-dusk font-ttg15-sans transition-colors duration-[800ms] has-checked:bg-ttg15-lamp has-checked:[&_.ttg15-glow]:opacity-100 motion-reduce:transition-none" aria-label="Skeuomorphic wall light switch">
<span aria-hidden="true" class="ttg15-glow pointer-events-none absolute top-[8%] left-1/2 -translate-x-1/2 w-[420px] h-[420px] rounded-full opacity-0 transition-opacity duration-[800ms] bg-radial from-ttg15-halo to-transparent to-65% motion-reduce:transition-none"></span>
<div class="relative z-10 flex flex-col items-center gap-4 w-32 py-6 rounded-xl bg-linear-170 from-ttg15-platehi to-ttg15-platelo shadow-[inset_0_2px_0_oklch(1_0_0/0.9),inset_0_-3px_5px_oklch(0.4_0.03_80/0.35),0_18px_34px_-14px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:-rotate-[38deg]"></span>
<span class="relative block w-[58px] h-26 p-[5px] rounded-lg bg-ttg15-well shadow-[inset_0_3px_7px_oklch(0.4_0.03_80/0.55)] perspective-[300px]">
<input type="checkbox" id="ttg-15-sw" role="switch" aria-label="Room light" class="peer sr-only">
<label for="ttg-15-sw" aria-hidden="true" class="block w-full h-full rounded-md cursor-pointer bg-linear-to-b from-ttg15-paddlelo via-ttg15-paddlehi via-55% to-ttg15-paddlemid shadow-[0_4px_8px_oklch(0.4_0.03_80/0.4),inset_0_1px_0_oklch(1_0_0/0.8)] origin-center rotate-x-[14deg] transition-transform duration-[220ms] ease-[cubic-bezier(0.5,1.4,0.6,1)] peer-checked:-rotate-x-[14deg] peer-checked:bg-linear-to-t peer-focus-visible:outline-3 peer-focus-visible:outline-ttg15-focus peer-focus-visible:outline-offset-4 motion-reduce:transition-none"></label>
</span>
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:rotate-[24deg]"></span>
</div>
<p class="relative z-10 text-[12px] tracking-[0.12em] uppercase text-ttg15-duskink transition-colors duration-[800ms] [.ttg-15:has(:checked)_&]:text-ttg15-lampink motion-reduce:transition-none">Flip it — the room lights up</p>
</section><section class="ttg-15 relative overflow-hidden w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg15-dusk font-ttg15-sans transition-colors duration-[800ms] has-checked:bg-ttg15-lamp has-checked:[&_.ttg15-glow]:opacity-100 motion-reduce:transition-none" aria-label="Skeuomorphic wall light switch">
<span aria-hidden="true" class="ttg15-glow pointer-events-none absolute top-[8%] left-1/2 -translate-x-1/2 w-[420px] h-[420px] rounded-full opacity-0 transition-opacity duration-[800ms] bg-radial from-ttg15-halo to-transparent to-65% motion-reduce:transition-none"></span>
<div class="relative z-10 flex flex-col items-center gap-4 w-32 py-6 rounded-xl bg-linear-170 from-ttg15-platehi to-ttg15-platelo shadow-[inset_0_2px_0_oklch(1_0_0/0.9),inset_0_-3px_5px_oklch(0.4_0.03_80/0.35),0_18px_34px_-14px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:-rotate-[38deg]"></span>
<span class="relative block w-[58px] h-26 p-[5px] rounded-lg bg-ttg15-well shadow-[inset_0_3px_7px_oklch(0.4_0.03_80/0.55)] perspective-[300px]">
<input type="checkbox" id="ttg-15-sw" role="switch" aria-label="Room light" class="peer sr-only">
<label for="ttg-15-sw" aria-hidden="true" class="block w-full h-full rounded-md cursor-pointer bg-linear-to-b from-ttg15-paddlelo via-ttg15-paddlehi via-55% to-ttg15-paddlemid shadow-[0_4px_8px_oklch(0.4_0.03_80/0.4),inset_0_1px_0_oklch(1_0_0/0.8)] origin-center rotate-x-[14deg] transition-transform duration-[220ms] ease-[cubic-bezier(0.5,1.4,0.6,1)] peer-checked:-rotate-x-[14deg] peer-checked:bg-linear-to-t peer-focus-visible:outline-3 peer-focus-visible:outline-ttg15-focus peer-focus-visible:outline-offset-4 motion-reduce:transition-none"></label>
</span>
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:rotate-[24deg]"></span>
</div>
<p class="relative z-10 text-[12px] tracking-[0.12em] uppercase text-ttg15-duskink transition-colors duration-[800ms] [.ttg-15:has(:checked)_&]:text-ttg15-lampink motion-reduce:transition-none">Flip it — the room lights up</p>
</section>@import "tailwindcss";
@theme {
--color-ttg15-dusk: #262a35;
--color-ttg15-dusk: oklch(0.28 0.02 265);
--color-ttg15-lamp: #f3e6c9;
--color-ttg15-lamp: oklch(0.93 0.05 85);
--color-ttg15-halo: #ffd682;
--color-ttg15-halo: oklch(0.88 0.13 82 / 0.55);
--color-ttg15-platehi: #f6f3ec;
--color-ttg15-platehi: oklch(0.96 0.008 85);
--color-ttg15-platelo: #d8d3c7;
--color-ttg15-platelo: oklch(0.87 0.012 85);
--color-ttg15-well: #c7c1b3;
--color-ttg15-well: oklch(0.81 0.014 85);
--color-ttg15-paddlehi: #f8f5ee;
--color-ttg15-paddlehi: oklch(0.97 0.006 85);
--color-ttg15-paddlemid: #efece3;
--color-ttg15-paddlemid: oklch(0.94 0.008 85);
--color-ttg15-paddlelo: #cdc7b9;
--color-ttg15-paddlelo: oklch(0.83 0.013 85);
--color-ttg15-screwhi: #e9e5db;
--color-ttg15-screwhi: oklch(0.93 0.008 85);
--color-ttg15-screwlo: #9e988a;
--color-ttg15-screwlo: oklch(0.67 0.014 85);
--color-ttg15-slot: #6c6659;
--color-ttg15-slot: oklch(0.49 0.012 85);
--color-ttg15-duskink: #a3a8b6;
--color-ttg15-duskink: oklch(0.72 0.014 265);
--color-ttg15-lampink: #7a6a45;
--color-ttg15-lampink: oklch(0.52 0.05 85);
--color-ttg15-focus: #5b8def;
--color-ttg15-focus: oklch(0.65 0.16 258);
--font-ttg15-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.ttg-15 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-ttg15-dusk: #262a35;
--color-ttg15-dusk: oklch(0.28 0.02 265);
--color-ttg15-lamp: #f3e6c9;
--color-ttg15-lamp: oklch(0.93 0.05 85);
--color-ttg15-halo: #ffd682;
--color-ttg15-halo: oklch(0.88 0.13 82 / 0.55);
--color-ttg15-platehi: #f6f3ec;
--color-ttg15-platehi: oklch(0.96 0.008 85);
--color-ttg15-platelo: #d8d3c7;
--color-ttg15-platelo: oklch(0.87 0.012 85);
--color-ttg15-well: #c7c1b3;
--color-ttg15-well: oklch(0.81 0.014 85);
--color-ttg15-paddlehi: #f8f5ee;
--color-ttg15-paddlehi: oklch(0.97 0.006 85);
--color-ttg15-paddlemid: #efece3;
--color-ttg15-paddlemid: oklch(0.94 0.008 85);
--color-ttg15-paddlelo: #cdc7b9;
--color-ttg15-paddlelo: oklch(0.83 0.013 85);
--color-ttg15-screwhi: #e9e5db;
--color-ttg15-screwhi: oklch(0.93 0.008 85);
--color-ttg15-screwlo: #9e988a;
--color-ttg15-screwlo: oklch(0.67 0.014 85);
--color-ttg15-slot: #6c6659;
--color-ttg15-slot: oklch(0.49 0.012 85);
--color-ttg15-duskink: #a3a8b6;
--color-ttg15-duskink: oklch(0.72 0.014 265);
--color-ttg15-lampink: #7a6a45;
--color-ttg15-lampink: oklch(0.52 0.05 85);
--color-ttg15-focus: #5b8def;
--color-ttg15-focus: oklch(0.65 0.16 258);
--font-ttg15-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.ttg-15 {
width: 100%;
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-ttg15-dusk: #262a35;
--color-ttg15-dusk: oklch(0.28 0.02 265);
--color-ttg15-lamp: #f3e6c9;
--color-ttg15-lamp: oklch(0.93 0.05 85);
--color-ttg15-halo: #ffd682;
--color-ttg15-halo: oklch(0.88 0.13 82 / 0.55);
--color-ttg15-platehi: #f6f3ec;
--color-ttg15-platehi: oklch(0.96 0.008 85);
--color-ttg15-platelo: #d8d3c7;
--color-ttg15-platelo: oklch(0.87 0.012 85);
--color-ttg15-well: #c7c1b3;
--color-ttg15-well: oklch(0.81 0.014 85);
--color-ttg15-paddlehi: #f8f5ee;
--color-ttg15-paddlehi: oklch(0.97 0.006 85);
--color-ttg15-paddlemid: #efece3;
--color-ttg15-paddlemid: oklch(0.94 0.008 85);
--color-ttg15-paddlelo: #cdc7b9;
--color-ttg15-paddlelo: oklch(0.83 0.013 85);
--color-ttg15-screwhi: #e9e5db;
--color-ttg15-screwhi: oklch(0.93 0.008 85);
--color-ttg15-screwlo: #9e988a;
--color-ttg15-screwlo: oklch(0.67 0.014 85);
--color-ttg15-slot: #6c6659;
--color-ttg15-slot: oklch(0.49 0.012 85);
--color-ttg15-duskink: #a3a8b6;
--color-ttg15-duskink: oklch(0.72 0.014 265);
--color-ttg15-lampink: #7a6a45;
--color-ttg15-lampink: oklch(0.52 0.05 85);
--color-ttg15-focus: #5b8def;
--color-ttg15-focus: oklch(0.65 0.16 258);
--font-ttg15-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.ttg-15 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-ttg15-dusk: #262a35;
--color-ttg15-dusk: oklch(0.28 0.02 265);
--color-ttg15-lamp: #f3e6c9;
--color-ttg15-lamp: oklch(0.93 0.05 85);
--color-ttg15-halo: #ffd682;
--color-ttg15-halo: oklch(0.88 0.13 82 / 0.55);
--color-ttg15-platehi: #f6f3ec;
--color-ttg15-platehi: oklch(0.96 0.008 85);
--color-ttg15-platelo: #d8d3c7;
--color-ttg15-platelo: oklch(0.87 0.012 85);
--color-ttg15-well: #c7c1b3;
--color-ttg15-well: oklch(0.81 0.014 85);
--color-ttg15-paddlehi: #f8f5ee;
--color-ttg15-paddlehi: oklch(0.97 0.006 85);
--color-ttg15-paddlemid: #efece3;
--color-ttg15-paddlemid: oklch(0.94 0.008 85);
--color-ttg15-paddlelo: #cdc7b9;
--color-ttg15-paddlelo: oklch(0.83 0.013 85);
--color-ttg15-screwhi: #e9e5db;
--color-ttg15-screwhi: oklch(0.93 0.008 85);
--color-ttg15-screwlo: #9e988a;
--color-ttg15-screwlo: oklch(0.67 0.014 85);
--color-ttg15-slot: #6c6659;
--color-ttg15-slot: oklch(0.49 0.012 85);
--color-ttg15-duskink: #a3a8b6;
--color-ttg15-duskink: oklch(0.72 0.014 265);
--color-ttg15-lampink: #7a6a45;
--color-ttg15-lampink: oklch(0.52 0.05 85);
--color-ttg15-focus: #5b8def;
--color-ttg15-focus: oklch(0.65 0.16 258);
--font-ttg15-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.ttg-15 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- v4's
bg-radialandbg-radial-[at_35%_30%]are what make this demo utility-only: the halo and both screw heads are radial gradients, and in v3 all three would have been arbitrary[background-image:...]values. - The paddle's gradient flip is
peer-checked:bg-linear-to-t— direction reversed, stops unchanged. The CSS version restated the whole gradient in the checked rule, which is the kind of duplication that drifts. - The caption's lamp-state colour needs an arbitrary ancestor selector (
[.ttg-15:has(:checked)_&]:) because it is a sibling of the plate, not a descendant of anything stateful — the one place this port is more verbose than the original. - UI change from the CSS original: the dusk background lifted from
#2b2e38tooklch(0.28 0.02 265)and its caption ink tooklch(0.72), taking the unlit state from 3.4:1 to 5.9:1 — previously the room was so dark the instruction to flip the switch was the hardest thing on screen to read.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Skeuomorphic Wall Light Switch with Room Relight
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-skeuomorphic-wall-light-switch-room-relight/
A wall-plate rocker with a bevelled plate, slotted screws and a paddle that tips in real perspective — and when you flip it the whole room warms from dusk to lamp-light, because the section watches the switch with has-checked:.
## HTML
```html
<section class="ttg-15 relative overflow-hidden w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg15-dusk font-ttg15-sans transition-colors duration-[800ms] has-checked:bg-ttg15-lamp has-checked:[&_.ttg15-glow]:opacity-100 motion-reduce:transition-none" aria-label="Skeuomorphic wall light switch">
<span aria-hidden="true" class="ttg15-glow pointer-events-none absolute top-[8%] left-1/2 -translate-x-1/2 w-[420px] h-[420px] rounded-full opacity-0 transition-opacity duration-[800ms] bg-radial from-ttg15-halo to-transparent to-65% motion-reduce:transition-none"></span>
<div class="relative z-10 flex flex-col items-center gap-4 w-32 py-6 rounded-xl bg-linear-170 from-ttg15-platehi to-ttg15-platelo shadow-[inset_0_2px_0_oklch(1_0_0/0.9),inset_0_-3px_5px_oklch(0.4_0.03_80/0.35),0_18px_34px_-14px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:-rotate-[38deg]"></span>
<span class="relative block w-[58px] h-26 p-[5px] rounded-lg bg-ttg15-well shadow-[inset_0_3px_7px_oklch(0.4_0.03_80/0.55)] perspective-[300px]">
<input type="checkbox" id="ttg-15-sw" role="switch" aria-label="Room light" class="peer sr-only">
<label for="ttg-15-sw" aria-hidden="true" class="block w-full h-full rounded-md cursor-pointer bg-linear-to-b from-ttg15-paddlelo via-ttg15-paddlehi via-55% to-ttg15-paddlemid shadow-[0_4px_8px_oklch(0.4_0.03_80/0.4),inset_0_1px_0_oklch(1_0_0/0.8)] origin-center rotate-x-[14deg] transition-transform duration-[220ms] ease-[cubic-bezier(0.5,1.4,0.6,1)] peer-checked:-rotate-x-[14deg] peer-checked:bg-linear-to-t peer-focus-visible:outline-3 peer-focus-visible:outline-ttg15-focus peer-focus-visible:outline-offset-4 motion-reduce:transition-none"></label>
</span>
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:rotate-[24deg]"></span>
</div>
<p class="relative z-10 text-[12px] tracking-[0.12em] uppercase text-ttg15-duskink transition-colors duration-[800ms] [.ttg-15:has(:checked)_&]:text-ttg15-lampink motion-reduce:transition-none">Flip it — the room lights up</p>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg15-dusk: #262a35;
--color-ttg15-dusk: oklch(0.28 0.02 265);
--color-ttg15-lamp: #f3e6c9;
--color-ttg15-lamp: oklch(0.93 0.05 85);
--color-ttg15-halo: #ffd682;
--color-ttg15-halo: oklch(0.88 0.13 82 / 0.55);
--color-ttg15-platehi: #f6f3ec;
--color-ttg15-platehi: oklch(0.96 0.008 85);
--color-ttg15-platelo: #d8d3c7;
--color-ttg15-platelo: oklch(0.87 0.012 85);
--color-ttg15-well: #c7c1b3;
--color-ttg15-well: oklch(0.81 0.014 85);
--color-ttg15-paddlehi: #f8f5ee;
--color-ttg15-paddlehi: oklch(0.97 0.006 85);
--color-ttg15-paddlemid: #efece3;
--color-ttg15-paddlemid: oklch(0.94 0.008 85);
--color-ttg15-paddlelo: #cdc7b9;
--color-ttg15-paddlelo: oklch(0.83 0.013 85);
--color-ttg15-screwhi: #e9e5db;
--color-ttg15-screwhi: oklch(0.93 0.008 85);
--color-ttg15-screwlo: #9e988a;
--color-ttg15-screwlo: oklch(0.67 0.014 85);
--color-ttg15-slot: #6c6659;
--color-ttg15-slot: oklch(0.49 0.012 85);
--color-ttg15-duskink: #a3a8b6;
--color-ttg15-duskink: oklch(0.72 0.014 265);
--color-ttg15-lampink: #7a6a45;
--color-ttg15-lampink: oklch(0.52 0.05 85);
--color-ttg15-focus: #5b8def;
--color-ttg15-focus: oklch(0.65 0.16 258);
--font-ttg15-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.ttg-15 {
width: 100%;
min-height: 100svh;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Skeuomorphic Wall Light Switch with Room Relight
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-skeuomorphic-wall-light-switch-room-relight/
A wall-plate rocker with a bevelled plate, slotted screws and a paddle that tips in real perspective — and when you flip it the whole room warms from dusk to lamp-light, because the section watches the switch with has-checked:.
## HTML
```html
<section class="ttg-15 relative overflow-hidden w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg15-dusk font-ttg15-sans transition-colors duration-[800ms] has-checked:bg-ttg15-lamp has-checked:[&_.ttg15-glow]:opacity-100 motion-reduce:transition-none" aria-label="Skeuomorphic wall light switch">
<span aria-hidden="true" class="ttg15-glow pointer-events-none absolute top-[8%] left-1/2 -translate-x-1/2 w-[420px] h-[420px] rounded-full opacity-0 transition-opacity duration-[800ms] bg-radial from-ttg15-halo to-transparent to-65% motion-reduce:transition-none"></span>
<div class="relative z-10 flex flex-col items-center gap-4 w-32 py-6 rounded-xl bg-linear-170 from-ttg15-platehi to-ttg15-platelo shadow-[inset_0_2px_0_oklch(1_0_0/0.9),inset_0_-3px_5px_oklch(0.4_0.03_80/0.35),0_18px_34px_-14px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:-rotate-[38deg]"></span>
<span class="relative block w-[58px] h-26 p-[5px] rounded-lg bg-ttg15-well shadow-[inset_0_3px_7px_oklch(0.4_0.03_80/0.55)] perspective-[300px]">
<input type="checkbox" id="ttg-15-sw" role="switch" aria-label="Room light" class="peer sr-only">
<label for="ttg-15-sw" aria-hidden="true" class="block w-full h-full rounded-md cursor-pointer bg-linear-to-b from-ttg15-paddlelo via-ttg15-paddlehi via-55% to-ttg15-paddlemid shadow-[0_4px_8px_oklch(0.4_0.03_80/0.4),inset_0_1px_0_oklch(1_0_0/0.8)] origin-center rotate-x-[14deg] transition-transform duration-[220ms] ease-[cubic-bezier(0.5,1.4,0.6,1)] peer-checked:-rotate-x-[14deg] peer-checked:bg-linear-to-t peer-focus-visible:outline-3 peer-focus-visible:outline-ttg15-focus peer-focus-visible:outline-offset-4 motion-reduce:transition-none"></label>
</span>
<span aria-hidden="true" class="relative w-[11px] h-[11px] rounded-full bg-radial-[at_35%_30%] from-ttg15-screwhi to-ttg15-screwlo shadow-[inset_0_-1px_2px_oklch(0.35_0.03_80/0.5)] before:content-[''] before:absolute before:top-[4.5px] before:left-[1.5px] before:right-[1.5px] before:h-[1.5px] before:rounded-[1px] before:bg-ttg15-slot before:rotate-[24deg]"></span>
</div>
<p class="relative z-10 text-[12px] tracking-[0.12em] uppercase text-ttg15-duskink transition-colors duration-[800ms] [.ttg-15:has(:checked)_&]:text-ttg15-lampink motion-reduce:transition-none">Flip it — the room lights up</p>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg15-dusk: #262a35;
--color-ttg15-dusk: oklch(0.28 0.02 265);
--color-ttg15-lamp: #f3e6c9;
--color-ttg15-lamp: oklch(0.93 0.05 85);
--color-ttg15-halo: #ffd682;
--color-ttg15-halo: oklch(0.88 0.13 82 / 0.55);
--color-ttg15-platehi: #f6f3ec;
--color-ttg15-platehi: oklch(0.96 0.008 85);
--color-ttg15-platelo: #d8d3c7;
--color-ttg15-platelo: oklch(0.87 0.012 85);
--color-ttg15-well: #c7c1b3;
--color-ttg15-well: oklch(0.81 0.014 85);
--color-ttg15-paddlehi: #f8f5ee;
--color-ttg15-paddlehi: oklch(0.97 0.006 85);
--color-ttg15-paddlemid: #efece3;
--color-ttg15-paddlemid: oklch(0.94 0.008 85);
--color-ttg15-paddlelo: #cdc7b9;
--color-ttg15-paddlelo: oklch(0.83 0.013 85);
--color-ttg15-screwhi: #e9e5db;
--color-ttg15-screwhi: oklch(0.93 0.008 85);
--color-ttg15-screwlo: #9e988a;
--color-ttg15-screwlo: oklch(0.67 0.014 85);
--color-ttg15-slot: #6c6659;
--color-ttg15-slot: oklch(0.49 0.012 85);
--color-ttg15-duskink: #a3a8b6;
--color-ttg15-duskink: oklch(0.72 0.014 265);
--color-ttg15-lampink: #7a6a45;
--color-ttg15-lampink: oklch(0.52 0.05 85);
--color-ttg15-focus: #5b8def;
--color-ttg15-focus: oklch(0.65 0.16 258);
--font-ttg15-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.ttg-15 {
width: 100%;
min-height: 100svh;
}
```How this works
The plate is layered light. A 170° linear gradient for the moulding, an inset white top edge and an inset dark bottom edge for the bevel, and a wide soft drop to lift it off the wall. Everything is a shadow; nothing is an image.
Two cues make a rocker. The paddle both rotates (14deg toward you → -14deg away) and flips its gradient vertically. Either alone reads as ambiguous; together they read unmistakably as a physical tip. Deliberately, the paddle never translates — a rocker that slides is a slider.
The payoff is the room. has-checked: on the section transitions the background from dusk to warm lamp over 800ms and blooms a radial glow above the plate. Both properties are colour and opacity only, so the drama costs nothing.
Make it yours
- Retheme the room through
--color-ttg15-duskand--color-ttg15-lamp. - European profile: make the paddle taller and narrower and reduce the tip to 10°.
- Add a 30ms click sample on
changefor the full skeuomorphic hit (three lines of JS). - Torx screws: swap the slot
before:for a rotated cross of two spans.
Gotchas — read before shipping
perspective-*goes on the paddle's PARENT. On the paddle itself the tip flattens into a vertical squash.- Ambient scene changes are decoration. The state signal for assistive tech stays on the checkbox, which is why the paddle is
aria-hiddenand the input carries the label. - An 800ms background transition is long enough to feel cinematic and short enough not to feel broken — but it must be disabled under
motion-reduce:, where a full-viewport colour sweep is genuinely unpleasant.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
3D transforms are universal; the room relight needs :has() (Firefox 121+) and degrades to a plain working rocker.