29 / 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 Radial VU Meter Mute Toggle
The waveform idea rebuilt as studio hardware: a radial VU meter whose needle sweeps while audio is live and drops to the peg when muted, over a masked conic arc that runs green through amber to red. Same job as demo 09, a completely different read.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="ttg-29 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-14 bg-ttg29-bg font-ttg29-sans" aria-label="Radial VU meter mute toggle">
<label for="ttg-29-sw" class="flex flex-col items-center gap-5 px-9 py-7 rounded-[24px] cursor-pointer bg-ttg29-card border border-ttg29-edge has-checked:[&_.ttg29-arc]:saturate-150 has-checked:[&_.ttg29-needle]:animate-ttg29-needle has-checked:[&_.ttg29-lamp]:bg-ttg29-live has-checked:[&_.ttg29-lamp]:shadow-[0_0_10px_var(--color-ttg29-live)] motion-reduce:has-checked:[&_.ttg29-needle]:animate-none motion-reduce:has-checked:[&_.ttg29-needle]:rotate-[8deg]">
<span class="relative grid place-items-center w-38 h-38 rounded-full bg-ttg29-face shadow-[inset_0_2px_10px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="ttg29-arc absolute inset-2 rounded-full saturate-50 transition-[filter] duration-500 [background:conic-gradient(from_230deg,var(--color-ttg29-ok)_0deg,var(--color-ttg29-ok)_70deg,var(--color-ttg29-warn)_110deg,var(--color-ttg29-hot)_145deg,transparent_150deg)] [mask:radial-gradient(circle,transparent_52px,#000_53px)]"></span>
<span aria-hidden="true" class="ttg29-needle absolute bottom-1/2 left-1/2 -ml-[1.5px] w-[3px] h-14 rounded-full bg-ttg29-needle origin-bottom rotate-[-52deg] transition-transform duration-500 ease-out shadow-[0_0_6px_oklch(0_0_0/0.5)]"></span>
<span aria-hidden="true" class="absolute bottom-1/2 left-1/2 -ml-2 -mb-2 w-4 h-4 rounded-full bg-ttg29-pivot shadow-[inset_0_-1px_2px_oklch(0_0_0/0.6)]"></span>
<span aria-hidden="true" class="ttg29-lamp absolute bottom-6 w-2 h-2 rounded-full bg-ttg29-lampoff transition-[background-color,box-shadow] duration-300"></span>
<input type="checkbox" id="ttg-29-sw" role="switch" checked aria-label="Studio monitor" class="appearance-none absolute inset-0 w-full h-full rounded-full bg-transparent cursor-pointer z-10 focus-visible:outline-3 focus-visible:outline-ttg29-live focus-visible:outline-offset-4">
</span>
<span class="flex flex-col items-center gap-0.5 text-center">
<span class="text-[15.5px] font-semibold text-ttg29-ink">Studio monitor</span>
<span class="font-ttg29-mono text-[11px] tracking-[0.14em] uppercase text-ttg29-mute">Needle swinging = live · pegged = muted</span>
</span>
</label>
</section><section class="ttg-29 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-14 bg-ttg29-bg font-ttg29-sans" aria-label="Radial VU meter mute toggle">
<label for="ttg-29-sw" class="flex flex-col items-center gap-5 px-9 py-7 rounded-[24px] cursor-pointer bg-ttg29-card border border-ttg29-edge has-checked:[&_.ttg29-arc]:saturate-150 has-checked:[&_.ttg29-needle]:animate-ttg29-needle has-checked:[&_.ttg29-lamp]:bg-ttg29-live has-checked:[&_.ttg29-lamp]:shadow-[0_0_10px_var(--color-ttg29-live)] motion-reduce:has-checked:[&_.ttg29-needle]:animate-none motion-reduce:has-checked:[&_.ttg29-needle]:rotate-[8deg]">
<span class="relative grid place-items-center w-38 h-38 rounded-full bg-ttg29-face shadow-[inset_0_2px_10px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="ttg29-arc absolute inset-2 rounded-full saturate-50 transition-[filter] duration-500 [background:conic-gradient(from_230deg,var(--color-ttg29-ok)_0deg,var(--color-ttg29-ok)_70deg,var(--color-ttg29-warn)_110deg,var(--color-ttg29-hot)_145deg,transparent_150deg)] [mask:radial-gradient(circle,transparent_52px,#000_53px)]"></span>
<span aria-hidden="true" class="ttg29-needle absolute bottom-1/2 left-1/2 -ml-[1.5px] w-[3px] h-14 rounded-full bg-ttg29-needle origin-bottom rotate-[-52deg] transition-transform duration-500 ease-out shadow-[0_0_6px_oklch(0_0_0/0.5)]"></span>
<span aria-hidden="true" class="absolute bottom-1/2 left-1/2 -ml-2 -mb-2 w-4 h-4 rounded-full bg-ttg29-pivot shadow-[inset_0_-1px_2px_oklch(0_0_0/0.6)]"></span>
<span aria-hidden="true" class="ttg29-lamp absolute bottom-6 w-2 h-2 rounded-full bg-ttg29-lampoff transition-[background-color,box-shadow] duration-300"></span>
<input type="checkbox" id="ttg-29-sw" role="switch" checked aria-label="Studio monitor" class="appearance-none absolute inset-0 w-full h-full rounded-full bg-transparent cursor-pointer z-10 focus-visible:outline-3 focus-visible:outline-ttg29-live focus-visible:outline-offset-4">
</span>
<span class="flex flex-col items-center gap-0.5 text-center">
<span class="text-[15.5px] font-semibold text-ttg29-ink">Studio monitor</span>
<span class="font-ttg29-mono text-[11px] tracking-[0.14em] uppercase text-ttg29-mute">Needle swinging = live · pegged = muted</span>
</span>
</label>
</section>@import "tailwindcss";
@theme {
--color-ttg29-bg: #0e1115;
--color-ttg29-bg: oklch(0.17 0.008 255);
--color-ttg29-card: #171b21;
--color-ttg29-card: oklch(0.23 0.01 255);
--color-ttg29-edge: #272d36;
--color-ttg29-edge: oklch(0.31 0.012 255);
--color-ttg29-face: #12161b;
--color-ttg29-face: oklch(0.2 0.01 255);
--color-ttg29-ok: #3ddc9a;
--color-ttg29-ok: oklch(0.82 0.16 165);
--color-ttg29-warn: #f0c04a;
--color-ttg29-warn: oklch(0.84 0.14 90);
--color-ttg29-hot: #ff5d4a;
--color-ttg29-hot: oklch(0.68 0.21 27);
--color-ttg29-needle: #f2f5fa;
--color-ttg29-needle: oklch(0.96 0.005 255);
--color-ttg29-pivot: #3b434f;
--color-ttg29-pivot: oklch(0.4 0.014 255);
--color-ttg29-live: #3ddc9a;
--color-ttg29-live: oklch(0.82 0.16 165);
--color-ttg29-lampoff: #2b323c;
--color-ttg29-lampoff: oklch(0.32 0.012 255);
--color-ttg29-ink: #d5dce6;
--color-ttg29-ink: oklch(0.9 0.01 255);
--color-ttg29-mute: #8f97a5;
--color-ttg29-mute: oklch(0.67 0.015 255);
--font-ttg29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg29-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--animate-ttg29-needle: ttg29-needle 1.7s ease-in-out infinite alternate;
}
@keyframes ttg29-needle {
0% {
transform: rotate(-14deg);
}
35% {
transform: rotate(22deg);
}
60% {
transform: rotate(2deg);
}
100% {
transform: rotate(34deg);
}
}
.ttg-29 {
width: 100%;
min-height: 100svh;
}
.ttg-29 input[type="checkbox"] {
-webkit-appearance: none;
}@import "tailwindcss";
@theme {
--color-ttg29-bg: #0e1115;
--color-ttg29-bg: oklch(0.17 0.008 255);
--color-ttg29-card: #171b21;
--color-ttg29-card: oklch(0.23 0.01 255);
--color-ttg29-edge: #272d36;
--color-ttg29-edge: oklch(0.31 0.012 255);
--color-ttg29-face: #12161b;
--color-ttg29-face: oklch(0.2 0.01 255);
--color-ttg29-ok: #3ddc9a;
--color-ttg29-ok: oklch(0.82 0.16 165);
--color-ttg29-warn: #f0c04a;
--color-ttg29-warn: oklch(0.84 0.14 90);
--color-ttg29-hot: #ff5d4a;
--color-ttg29-hot: oklch(0.68 0.21 27);
--color-ttg29-needle: #f2f5fa;
--color-ttg29-needle: oklch(0.96 0.005 255);
--color-ttg29-pivot: #3b434f;
--color-ttg29-pivot: oklch(0.4 0.014 255);
--color-ttg29-live: #3ddc9a;
--color-ttg29-live: oklch(0.82 0.16 165);
--color-ttg29-lampoff: #2b323c;
--color-ttg29-lampoff: oklch(0.32 0.012 255);
--color-ttg29-ink: #d5dce6;
--color-ttg29-ink: oklch(0.9 0.01 255);
--color-ttg29-mute: #8f97a5;
--color-ttg29-mute: oklch(0.67 0.015 255);
--font-ttg29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg29-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--animate-ttg29-needle: ttg29-needle 1.7s ease-in-out infinite alternate;
}
@keyframes ttg29-needle {
0% {
transform: rotate(-14deg);
}
35% {
transform: rotate(22deg);
}
60% {
transform: rotate(2deg);
}
100% {
transform: rotate(34deg);
}
}
.ttg-29 {
width: 100%;
min-height: 100svh;
}
.ttg-29 input[type="checkbox"] {
-webkit-appearance: none;
}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-ttg29-bg: #0e1115;
--color-ttg29-bg: oklch(0.17 0.008 255);
--color-ttg29-card: #171b21;
--color-ttg29-card: oklch(0.23 0.01 255);
--color-ttg29-edge: #272d36;
--color-ttg29-edge: oklch(0.31 0.012 255);
--color-ttg29-face: #12161b;
--color-ttg29-face: oklch(0.2 0.01 255);
--color-ttg29-ok: #3ddc9a;
--color-ttg29-ok: oklch(0.82 0.16 165);
--color-ttg29-warn: #f0c04a;
--color-ttg29-warn: oklch(0.84 0.14 90);
--color-ttg29-hot: #ff5d4a;
--color-ttg29-hot: oklch(0.68 0.21 27);
--color-ttg29-needle: #f2f5fa;
--color-ttg29-needle: oklch(0.96 0.005 255);
--color-ttg29-pivot: #3b434f;
--color-ttg29-pivot: oklch(0.4 0.014 255);
--color-ttg29-live: #3ddc9a;
--color-ttg29-live: oklch(0.82 0.16 165);
--color-ttg29-lampoff: #2b323c;
--color-ttg29-lampoff: oklch(0.32 0.012 255);
--color-ttg29-ink: #d5dce6;
--color-ttg29-ink: oklch(0.9 0.01 255);
--color-ttg29-mute: #8f97a5;
--color-ttg29-mute: oklch(0.67 0.015 255);
--font-ttg29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg29-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--animate-ttg29-needle: ttg29-needle 1.7s ease-in-out infinite alternate;
}
@keyframes ttg29-needle {
0% {
transform: rotate(-14deg);
}
35% {
transform: rotate(22deg);
}
60% {
transform: rotate(2deg);
}
100% {
transform: rotate(34deg);
}
}
.ttg-29 {
width: 100%;
min-height: 100svh;
}
.ttg-29 input[type="checkbox"] {
-webkit-appearance: none;
}@import "tailwindcss";
@theme {
--color-ttg29-bg: #0e1115;
--color-ttg29-bg: oklch(0.17 0.008 255);
--color-ttg29-card: #171b21;
--color-ttg29-card: oklch(0.23 0.01 255);
--color-ttg29-edge: #272d36;
--color-ttg29-edge: oklch(0.31 0.012 255);
--color-ttg29-face: #12161b;
--color-ttg29-face: oklch(0.2 0.01 255);
--color-ttg29-ok: #3ddc9a;
--color-ttg29-ok: oklch(0.82 0.16 165);
--color-ttg29-warn: #f0c04a;
--color-ttg29-warn: oklch(0.84 0.14 90);
--color-ttg29-hot: #ff5d4a;
--color-ttg29-hot: oklch(0.68 0.21 27);
--color-ttg29-needle: #f2f5fa;
--color-ttg29-needle: oklch(0.96 0.005 255);
--color-ttg29-pivot: #3b434f;
--color-ttg29-pivot: oklch(0.4 0.014 255);
--color-ttg29-live: #3ddc9a;
--color-ttg29-live: oklch(0.82 0.16 165);
--color-ttg29-lampoff: #2b323c;
--color-ttg29-lampoff: oklch(0.32 0.012 255);
--color-ttg29-ink: #d5dce6;
--color-ttg29-ink: oklch(0.9 0.01 255);
--color-ttg29-mute: #8f97a5;
--color-ttg29-mute: oklch(0.67 0.015 255);
--font-ttg29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg29-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--animate-ttg29-needle: ttg29-needle 1.7s ease-in-out infinite alternate;
}
@keyframes ttg29-needle {
0% {
transform: rotate(-14deg);
}
35% {
transform: rotate(22deg);
}
60% {
transform: rotate(2deg);
}
100% {
transform: rotate(34deg);
}
}
.ttg-29 {
width: 100%;
min-height: 100svh;
}
.ttg-29 input[type="checkbox"] {
-webkit-appearance: none;
}<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&family=JetBrains+Mono:wght@400;500&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&family=JetBrains+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- A gauge with no SVG:
conic-gradientfor the scale, a radial-gradient mask for the ring, one rotated div for the needle. The mask is the piece people reach for SVG to do, and it is a single arbitrary property. - The keyframes deliberately have uneven beats (−14 → 22 → 2 → 34) so the swing reads as signal rather than as a metronome — the same reasoning as the staggered EQ delays in demo 09, achieved inside one element.
saturate-50at rest andhas-checked:saturate-150live is a cheaper state signal than recolouring the arc, and it keeps one gradient definition for both states.- UI upgrade over demo 09: state is now carried by needle position, arc saturation AND a bezel lamp, the control reads as a piece of hardware rather than a pill, and the reduced-motion path parks the needle mid-scale instead of flattening the only live signal.
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 Radial VU Meter Mute Toggle
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-radial-vu-meter-mute-toggle/
The waveform idea rebuilt as studio hardware: a radial VU meter whose needle sweeps while audio is live and drops to the peg when muted, over a masked conic arc that runs green through amber to red. Same job as demo 09, a completely different read.
## HTML
```html
<section class="ttg-29 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-14 bg-ttg29-bg font-ttg29-sans" aria-label="Radial VU meter mute toggle">
<label for="ttg-29-sw" class="flex flex-col items-center gap-5 px-9 py-7 rounded-[24px] cursor-pointer bg-ttg29-card border border-ttg29-edge has-checked:[&_.ttg29-arc]:saturate-150 has-checked:[&_.ttg29-needle]:animate-ttg29-needle has-checked:[&_.ttg29-lamp]:bg-ttg29-live has-checked:[&_.ttg29-lamp]:shadow-[0_0_10px_var(--color-ttg29-live)] motion-reduce:has-checked:[&_.ttg29-needle]:animate-none motion-reduce:has-checked:[&_.ttg29-needle]:rotate-[8deg]">
<span class="relative grid place-items-center w-38 h-38 rounded-full bg-ttg29-face shadow-[inset_0_2px_10px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="ttg29-arc absolute inset-2 rounded-full saturate-50 transition-[filter] duration-500 [background:conic-gradient(from_230deg,var(--color-ttg29-ok)_0deg,var(--color-ttg29-ok)_70deg,var(--color-ttg29-warn)_110deg,var(--color-ttg29-hot)_145deg,transparent_150deg)] [mask:radial-gradient(circle,transparent_52px,#000_53px)]"></span>
<span aria-hidden="true" class="ttg29-needle absolute bottom-1/2 left-1/2 -ml-[1.5px] w-[3px] h-14 rounded-full bg-ttg29-needle origin-bottom rotate-[-52deg] transition-transform duration-500 ease-out shadow-[0_0_6px_oklch(0_0_0/0.5)]"></span>
<span aria-hidden="true" class="absolute bottom-1/2 left-1/2 -ml-2 -mb-2 w-4 h-4 rounded-full bg-ttg29-pivot shadow-[inset_0_-1px_2px_oklch(0_0_0/0.6)]"></span>
<span aria-hidden="true" class="ttg29-lamp absolute bottom-6 w-2 h-2 rounded-full bg-ttg29-lampoff transition-[background-color,box-shadow] duration-300"></span>
<input type="checkbox" id="ttg-29-sw" role="switch" checked aria-label="Studio monitor" class="appearance-none absolute inset-0 w-full h-full rounded-full bg-transparent cursor-pointer z-10 focus-visible:outline-3 focus-visible:outline-ttg29-live focus-visible:outline-offset-4">
</span>
<span class="flex flex-col items-center gap-0.5 text-center">
<span class="text-[15.5px] font-semibold text-ttg29-ink">Studio monitor</span>
<span class="font-ttg29-mono text-[11px] tracking-[0.14em] uppercase text-ttg29-mute">Needle swinging = live · pegged = muted</span>
</span>
</label>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg29-bg: #0e1115;
--color-ttg29-bg: oklch(0.17 0.008 255);
--color-ttg29-card: #171b21;
--color-ttg29-card: oklch(0.23 0.01 255);
--color-ttg29-edge: #272d36;
--color-ttg29-edge: oklch(0.31 0.012 255);
--color-ttg29-face: #12161b;
--color-ttg29-face: oklch(0.2 0.01 255);
--color-ttg29-ok: #3ddc9a;
--color-ttg29-ok: oklch(0.82 0.16 165);
--color-ttg29-warn: #f0c04a;
--color-ttg29-warn: oklch(0.84 0.14 90);
--color-ttg29-hot: #ff5d4a;
--color-ttg29-hot: oklch(0.68 0.21 27);
--color-ttg29-needle: #f2f5fa;
--color-ttg29-needle: oklch(0.96 0.005 255);
--color-ttg29-pivot: #3b434f;
--color-ttg29-pivot: oklch(0.4 0.014 255);
--color-ttg29-live: #3ddc9a;
--color-ttg29-live: oklch(0.82 0.16 165);
--color-ttg29-lampoff: #2b323c;
--color-ttg29-lampoff: oklch(0.32 0.012 255);
--color-ttg29-ink: #d5dce6;
--color-ttg29-ink: oklch(0.9 0.01 255);
--color-ttg29-mute: #8f97a5;
--color-ttg29-mute: oklch(0.67 0.015 255);
--font-ttg29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg29-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--animate-ttg29-needle: ttg29-needle 1.7s ease-in-out infinite alternate;
}
@keyframes ttg29-needle {
0% {
transform: rotate(-14deg);
}
35% {
transform: rotate(22deg);
}
60% {
transform: rotate(2deg);
}
100% {
transform: rotate(34deg);
}
}
.ttg-29 {
width: 100%;
min-height: 100svh;
}
.ttg-29 input[type="checkbox"] {
-webkit-appearance: none;
}
```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 Radial VU Meter Mute Toggle
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-radial-vu-meter-mute-toggle/
The waveform idea rebuilt as studio hardware: a radial VU meter whose needle sweeps while audio is live and drops to the peg when muted, over a masked conic arc that runs green through amber to red. Same job as demo 09, a completely different read.
## HTML
```html
<section class="ttg-29 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-14 bg-ttg29-bg font-ttg29-sans" aria-label="Radial VU meter mute toggle">
<label for="ttg-29-sw" class="flex flex-col items-center gap-5 px-9 py-7 rounded-[24px] cursor-pointer bg-ttg29-card border border-ttg29-edge has-checked:[&_.ttg29-arc]:saturate-150 has-checked:[&_.ttg29-needle]:animate-ttg29-needle has-checked:[&_.ttg29-lamp]:bg-ttg29-live has-checked:[&_.ttg29-lamp]:shadow-[0_0_10px_var(--color-ttg29-live)] motion-reduce:has-checked:[&_.ttg29-needle]:animate-none motion-reduce:has-checked:[&_.ttg29-needle]:rotate-[8deg]">
<span class="relative grid place-items-center w-38 h-38 rounded-full bg-ttg29-face shadow-[inset_0_2px_10px_oklch(0_0_0/0.55)]">
<span aria-hidden="true" class="ttg29-arc absolute inset-2 rounded-full saturate-50 transition-[filter] duration-500 [background:conic-gradient(from_230deg,var(--color-ttg29-ok)_0deg,var(--color-ttg29-ok)_70deg,var(--color-ttg29-warn)_110deg,var(--color-ttg29-hot)_145deg,transparent_150deg)] [mask:radial-gradient(circle,transparent_52px,#000_53px)]"></span>
<span aria-hidden="true" class="ttg29-needle absolute bottom-1/2 left-1/2 -ml-[1.5px] w-[3px] h-14 rounded-full bg-ttg29-needle origin-bottom rotate-[-52deg] transition-transform duration-500 ease-out shadow-[0_0_6px_oklch(0_0_0/0.5)]"></span>
<span aria-hidden="true" class="absolute bottom-1/2 left-1/2 -ml-2 -mb-2 w-4 h-4 rounded-full bg-ttg29-pivot shadow-[inset_0_-1px_2px_oklch(0_0_0/0.6)]"></span>
<span aria-hidden="true" class="ttg29-lamp absolute bottom-6 w-2 h-2 rounded-full bg-ttg29-lampoff transition-[background-color,box-shadow] duration-300"></span>
<input type="checkbox" id="ttg-29-sw" role="switch" checked aria-label="Studio monitor" class="appearance-none absolute inset-0 w-full h-full rounded-full bg-transparent cursor-pointer z-10 focus-visible:outline-3 focus-visible:outline-ttg29-live focus-visible:outline-offset-4">
</span>
<span class="flex flex-col items-center gap-0.5 text-center">
<span class="text-[15.5px] font-semibold text-ttg29-ink">Studio monitor</span>
<span class="font-ttg29-mono text-[11px] tracking-[0.14em] uppercase text-ttg29-mute">Needle swinging = live · pegged = muted</span>
</span>
</label>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg29-bg: #0e1115;
--color-ttg29-bg: oklch(0.17 0.008 255);
--color-ttg29-card: #171b21;
--color-ttg29-card: oklch(0.23 0.01 255);
--color-ttg29-edge: #272d36;
--color-ttg29-edge: oklch(0.31 0.012 255);
--color-ttg29-face: #12161b;
--color-ttg29-face: oklch(0.2 0.01 255);
--color-ttg29-ok: #3ddc9a;
--color-ttg29-ok: oklch(0.82 0.16 165);
--color-ttg29-warn: #f0c04a;
--color-ttg29-warn: oklch(0.84 0.14 90);
--color-ttg29-hot: #ff5d4a;
--color-ttg29-hot: oklch(0.68 0.21 27);
--color-ttg29-needle: #f2f5fa;
--color-ttg29-needle: oklch(0.96 0.005 255);
--color-ttg29-pivot: #3b434f;
--color-ttg29-pivot: oklch(0.4 0.014 255);
--color-ttg29-live: #3ddc9a;
--color-ttg29-live: oklch(0.82 0.16 165);
--color-ttg29-lampoff: #2b323c;
--color-ttg29-lampoff: oklch(0.32 0.012 255);
--color-ttg29-ink: #d5dce6;
--color-ttg29-ink: oklch(0.9 0.01 255);
--color-ttg29-mute: #8f97a5;
--color-ttg29-mute: oklch(0.67 0.015 255);
--font-ttg29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg29-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--animate-ttg29-needle: ttg29-needle 1.7s ease-in-out infinite alternate;
}
@keyframes ttg29-needle {
0% {
transform: rotate(-14deg);
}
35% {
transform: rotate(22deg);
}
60% {
transform: rotate(2deg);
}
100% {
transform: rotate(34deg);
}
}
.ttg-29 {
width: 100%;
min-height: 100svh;
}
.ttg-29 input[type="checkbox"] {
-webkit-appearance: none;
}
```How this works
The arc is one conic gradient, masked into a ring. Colour stops run green → amber → red across 180°; a radial-gradient mask removes the middle, leaving a scale. Rotating the whole disc puts the scale where a VU meter's is, and nothing about it is an image.
The needle is one element with a transform origin. origin-bottom plus a rotation keyframe that never repeats exactly, because the swing and the return use different durations — the same trick as the five EQ bars, at one tenth the elements.
Muted is a peg, not a stop. Unchecked, the needle transitions to -52deg and rests there; the arc desaturates and the bezel lamp dies. Three signals, so the state survives greyscale and reduced motion.
Make it yours
- Rescale the meter by changing the disc size and the mask's two radii together.
- Stereo: two meters sharing one switch, with the second needle on a slight delay.
- Peak lamp: a small red dot that lights when the needle crosses −3dB (a second keyframe).
- Vintage cream face: swap the two surface tokens; the arc colours stay.
Gotchas — read before shipping
- The mask radii must track the disc size, or the ring thickness drifts when you scale it.
- Keep the needle's
origin-bottomexactly at the pivot dot, or it wobbles off-centre at large angles. - An infinite needle sweep is motion in the user's peripheral vision — the reduced-motion path parks it mid-scale rather than removing the live signal.
- Do not animate the conic gradient's stops; animate the needle. Gradient interpolation on a masked disc repaints the whole ring every frame.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
conic-gradient and CSS masks are supported in all evergreen browsers; :has() (Firefox 121+) lights the bezel.