17 / 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 Brutalist Snap Toggle with Hard Offset Shadow
Anti-animation as a statement: 3px black borders, a hard 6px offset shadow, and a block thumb that does not glide — it SNAPS across in a single frame via ease-[steps(1,end)]. Pressing shoves the whole card into its own shadow.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="ttg-17 w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg17-bg font-ttg17-mono" aria-label="Brutalist snap toggle">
<label for="ttg-17-sw" class="flex items-center gap-8 px-7 py-6 bg-white border-[3px] border-ttg17-ink cursor-pointer shadow-[6px_6px_0_var(--color-ttg17-ink)] transition-[transform,box-shadow] duration-[90ms] ease-[steps(1,end)] active:translate-x-[3px] active:translate-y-[3px] active:shadow-[3px_3px_0_var(--color-ttg17-ink)] motion-reduce:transition-none">
<span class="text-[15px] font-bold tracking-[0.14em] text-ttg17-ink">NEWSLETTER</span>
<span class="relative block">
<input type="checkbox" id="ttg-17-sw" role="switch" aria-label="Newsletter" class="peer appearance-none block w-26 h-12 border-[3px] border-ttg17-ink bg-white cursor-pointer relative transition-colors duration-[90ms] ease-[steps(1,end)] before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:w-9 before:h-9 before:bg-ttg17-ink before:transition-transform before:duration-[90ms] before:ease-[steps(1,end)] checked:bg-ttg17-signal checked:before:translate-x-14 focus-visible:outline-4 focus-visible:outline-ttg17-ink focus-visible:outline-offset-4 motion-reduce:transition-none motion-reduce:before:transition-none">
<span aria-hidden="true" class="absolute inset-[3px] block pointer-events-none text-[13px] font-extrabold tracking-[0.1em] text-ttg17-ink peer-checked:[&_.ttg17-off]:hidden peer-checked:[&_.ttg17-on]:block">
<i class="ttg17-off not-italic absolute top-1/2 right-2.5 -translate-y-1/2">OFF</i>
<i class="ttg17-on not-italic hidden absolute top-1/2 left-2.5 -translate-y-1/2">ON</i>
</span>
</span>
</label>
<p class="text-[12px] tracking-[0.1em] text-ttg17-mute">steps(1) — it snaps, it does not glide</p>
</section><section class="ttg-17 w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg17-bg font-ttg17-mono" aria-label="Brutalist snap toggle">
<label for="ttg-17-sw" class="flex items-center gap-8 px-7 py-6 bg-white border-[3px] border-ttg17-ink cursor-pointer shadow-[6px_6px_0_var(--color-ttg17-ink)] transition-[transform,box-shadow] duration-[90ms] ease-[steps(1,end)] active:translate-x-[3px] active:translate-y-[3px] active:shadow-[3px_3px_0_var(--color-ttg17-ink)] motion-reduce:transition-none">
<span class="text-[15px] font-bold tracking-[0.14em] text-ttg17-ink">NEWSLETTER</span>
<span class="relative block">
<input type="checkbox" id="ttg-17-sw" role="switch" aria-label="Newsletter" class="peer appearance-none block w-26 h-12 border-[3px] border-ttg17-ink bg-white cursor-pointer relative transition-colors duration-[90ms] ease-[steps(1,end)] before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:w-9 before:h-9 before:bg-ttg17-ink before:transition-transform before:duration-[90ms] before:ease-[steps(1,end)] checked:bg-ttg17-signal checked:before:translate-x-14 focus-visible:outline-4 focus-visible:outline-ttg17-ink focus-visible:outline-offset-4 motion-reduce:transition-none motion-reduce:before:transition-none">
<span aria-hidden="true" class="absolute inset-[3px] block pointer-events-none text-[13px] font-extrabold tracking-[0.1em] text-ttg17-ink peer-checked:[&_.ttg17-off]:hidden peer-checked:[&_.ttg17-on]:block">
<i class="ttg17-off not-italic absolute top-1/2 right-2.5 -translate-y-1/2">OFF</i>
<i class="ttg17-on not-italic hidden absolute top-1/2 left-2.5 -translate-y-1/2">ON</i>
</span>
</span>
</label>
<p class="text-[12px] tracking-[0.1em] text-ttg17-mute">steps(1) — it snaps, it does not glide</p>
</section>@import "tailwindcss";
@theme {
--color-ttg17-bg: #efece4;
--color-ttg17-bg: oklch(0.94 0.01 90);
--color-ttg17-ink: #000000;
--color-ttg17-ink: oklch(0 0 0);
--color-ttg17-signal: #ffe600;
--color-ttg17-signal: oklch(0.91 0.19 103);
--color-ttg17-mute: #6f6a5d;
--color-ttg17-mute: oklch(0.52 0.02 90);
--font-ttg17-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
.ttg-17 {
width: 100%;
min-height: 100svh;
}
.ttg-17 input[type="checkbox"] {
-webkit-appearance: none;
}@import "tailwindcss";
@theme {
--color-ttg17-bg: #efece4;
--color-ttg17-bg: oklch(0.94 0.01 90);
--color-ttg17-ink: #000000;
--color-ttg17-ink: oklch(0 0 0);
--color-ttg17-signal: #ffe600;
--color-ttg17-signal: oklch(0.91 0.19 103);
--color-ttg17-mute: #6f6a5d;
--color-ttg17-mute: oklch(0.52 0.02 90);
--font-ttg17-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
.ttg-17 {
width: 100%;
min-height: 100svh;
}
.ttg-17 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-ttg17-bg: #efece4;
--color-ttg17-bg: oklch(0.94 0.01 90);
--color-ttg17-ink: #000000;
--color-ttg17-ink: oklch(0 0 0);
--color-ttg17-signal: #ffe600;
--color-ttg17-signal: oklch(0.91 0.19 103);
--color-ttg17-mute: #6f6a5d;
--color-ttg17-mute: oklch(0.52 0.02 90);
--font-ttg17-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
.ttg-17 {
width: 100%;
min-height: 100svh;
}
.ttg-17 input[type="checkbox"] {
-webkit-appearance: none;
}@import "tailwindcss";
@theme {
--color-ttg17-bg: #efece4;
--color-ttg17-bg: oklch(0.94 0.01 90);
--color-ttg17-ink: #000000;
--color-ttg17-ink: oklch(0 0 0);
--color-ttg17-signal: #ffe600;
--color-ttg17-signal: oklch(0.91 0.19 103);
--color-ttg17-mute: #6f6a5d;
--color-ttg17-mute: oklch(0.52 0.02 90);
--font-ttg17-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
.ttg-17 {
width: 100%;
min-height: 100svh;
}
.ttg-17 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=JetBrains+Mono:wght@400;500;700;800&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=JetBrains+Mono:wght@400;500;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
ease-[steps(1,end)]on three separate transitions (card transform, card shadow, thumb transform) is the port's whole personality. An--ease-*token would have been tidier, but repeating it is the more honest signal that every motion in this demo is stepped by choice.- Referencing
var(--color-ttg17-ink)inside the hard shadow keeps the black in one place. It reads as over-engineering for a colour that will never change — until someone builds the dark variant, where the offset becomes white. - The legend swap uses
hidden/blockrather than opacity, deliberately: a crossfade would contradict a control whose thesis is that states do not blend. - UI change from the CSS original: the caption ink went from
#8a8578tooklch(0.52 0.02 90)for 5.8:1, and the signal yellow was re-expressed in oklch at 0.91 lightness so black legends inside the on-state track hold 14:1 — the original#ffe600was close, but drifted under colour-managed displays.
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 Brutalist Snap Toggle with Hard Offset Shadow
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-brutalist-snap-toggle-hard-offset-shadow/
Anti-animation as a statement: 3px black borders, a hard 6px offset shadow, and a block thumb that does not glide — it SNAPS across in a single frame via ease-[steps(1,end)]. Pressing shoves the whole card into its own shadow.
## HTML
```html
<section class="ttg-17 w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg17-bg font-ttg17-mono" aria-label="Brutalist snap toggle">
<label for="ttg-17-sw" class="flex items-center gap-8 px-7 py-6 bg-white border-[3px] border-ttg17-ink cursor-pointer shadow-[6px_6px_0_var(--color-ttg17-ink)] transition-[transform,box-shadow] duration-[90ms] ease-[steps(1,end)] active:translate-x-[3px] active:translate-y-[3px] active:shadow-[3px_3px_0_var(--color-ttg17-ink)] motion-reduce:transition-none">
<span class="text-[15px] font-bold tracking-[0.14em] text-ttg17-ink">NEWSLETTER</span>
<span class="relative block">
<input type="checkbox" id="ttg-17-sw" role="switch" aria-label="Newsletter" class="peer appearance-none block w-26 h-12 border-[3px] border-ttg17-ink bg-white cursor-pointer relative transition-colors duration-[90ms] ease-[steps(1,end)] before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:w-9 before:h-9 before:bg-ttg17-ink before:transition-transform before:duration-[90ms] before:ease-[steps(1,end)] checked:bg-ttg17-signal checked:before:translate-x-14 focus-visible:outline-4 focus-visible:outline-ttg17-ink focus-visible:outline-offset-4 motion-reduce:transition-none motion-reduce:before:transition-none">
<span aria-hidden="true" class="absolute inset-[3px] block pointer-events-none text-[13px] font-extrabold tracking-[0.1em] text-ttg17-ink peer-checked:[&_.ttg17-off]:hidden peer-checked:[&_.ttg17-on]:block">
<i class="ttg17-off not-italic absolute top-1/2 right-2.5 -translate-y-1/2">OFF</i>
<i class="ttg17-on not-italic hidden absolute top-1/2 left-2.5 -translate-y-1/2">ON</i>
</span>
</span>
</label>
<p class="text-[12px] tracking-[0.1em] text-ttg17-mute">steps(1) — it snaps, it does not glide</p>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg17-bg: #efece4;
--color-ttg17-bg: oklch(0.94 0.01 90);
--color-ttg17-ink: #000000;
--color-ttg17-ink: oklch(0 0 0);
--color-ttg17-signal: #ffe600;
--color-ttg17-signal: oklch(0.91 0.19 103);
--color-ttg17-mute: #6f6a5d;
--color-ttg17-mute: oklch(0.52 0.02 90);
--font-ttg17-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
.ttg-17 {
width: 100%;
min-height: 100svh;
}
.ttg-17 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 Brutalist Snap Toggle with Hard Offset Shadow
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-brutalist-snap-toggle-hard-offset-shadow/
Anti-animation as a statement: 3px black borders, a hard 6px offset shadow, and a block thumb that does not glide — it SNAPS across in a single frame via ease-[steps(1,end)]. Pressing shoves the whole card into its own shadow.
## HTML
```html
<section class="ttg-17 w-full min-h-svh flex flex-col items-center justify-center gap-8 px-5 py-14 bg-ttg17-bg font-ttg17-mono" aria-label="Brutalist snap toggle">
<label for="ttg-17-sw" class="flex items-center gap-8 px-7 py-6 bg-white border-[3px] border-ttg17-ink cursor-pointer shadow-[6px_6px_0_var(--color-ttg17-ink)] transition-[transform,box-shadow] duration-[90ms] ease-[steps(1,end)] active:translate-x-[3px] active:translate-y-[3px] active:shadow-[3px_3px_0_var(--color-ttg17-ink)] motion-reduce:transition-none">
<span class="text-[15px] font-bold tracking-[0.14em] text-ttg17-ink">NEWSLETTER</span>
<span class="relative block">
<input type="checkbox" id="ttg-17-sw" role="switch" aria-label="Newsletter" class="peer appearance-none block w-26 h-12 border-[3px] border-ttg17-ink bg-white cursor-pointer relative transition-colors duration-[90ms] ease-[steps(1,end)] before:content-[''] before:absolute before:top-[3px] before:left-[3px] before:w-9 before:h-9 before:bg-ttg17-ink before:transition-transform before:duration-[90ms] before:ease-[steps(1,end)] checked:bg-ttg17-signal checked:before:translate-x-14 focus-visible:outline-4 focus-visible:outline-ttg17-ink focus-visible:outline-offset-4 motion-reduce:transition-none motion-reduce:before:transition-none">
<span aria-hidden="true" class="absolute inset-[3px] block pointer-events-none text-[13px] font-extrabold tracking-[0.1em] text-ttg17-ink peer-checked:[&_.ttg17-off]:hidden peer-checked:[&_.ttg17-on]:block">
<i class="ttg17-off not-italic absolute top-1/2 right-2.5 -translate-y-1/2">OFF</i>
<i class="ttg17-on not-italic hidden absolute top-1/2 left-2.5 -translate-y-1/2">ON</i>
</span>
</span>
</label>
<p class="text-[12px] tracking-[0.1em] text-ttg17-mute">steps(1) — it snaps, it does not glide</p>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg17-bg: #efece4;
--color-ttg17-bg: oklch(0.94 0.01 90);
--color-ttg17-ink: #000000;
--color-ttg17-ink: oklch(0 0 0);
--color-ttg17-signal: #ffe600;
--color-ttg17-signal: oklch(0.91 0.19 103);
--color-ttg17-mute: #6f6a5d;
--color-ttg17-mute: oklch(0.52 0.02 90);
--font-ttg17-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
.ttg-17 {
width: 100%;
min-height: 100svh;
}
.ttg-17 input[type="checkbox"] {
-webkit-appearance: none;
}
```How this works
Stepped timing is the design. steps(1,end) means the thumb has no intermediate frames: it is here, then it is there. Every other demo in this collection spends its craft on the journey; this one deletes the journey on purpose. Resist the urge to 'fix' it with easing.
The shadow is a solid, not a blur. 6px 6px 0 with zero blur radius. One pixel of blur and the whole thing reads as a mistake rather than a style — brutalism has no soft edges to hide behind.
Contrast is the accessibility payoff. Pure black on white at 3px weight clears 21:1, the legends are 800-weight mono, and the focus ring is a 4px black outline at 4px offset. A style built on maximum contrast gets accessibility almost for free.
Make it yours
- Swap signal yellow for brutalist pink (
oklch(0.75 0.2 340)) or acid green. - Go fully instant:
transition-noneeverywhere. - Scale up: keep shadow offset at 2× border width, always.
- Stamp a catalogue index (
01/) before the label for editorial styling.
Gotchas — read before shipping
steps(1,end)is deliberate. Reviewers will report it as a bug; it is not.- Hard shadows need zero blur and a fully opaque colour — a translucent black reads as a drop shadow, not a printed offset.
- Keep the on-colour's contrast with black text above 7:1. Signal yellow works; a mid-saturation brand colour usually does not.
- Do not soften the focus ring to match the aesthetic — invert it instead.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Borders, solid shadows and steps() timing work in effectively every browser that ships CSS3; the floor is Tailwind v4's generated sheet alone.