47 Tailwind CSS Toggles & Switches
04 / 47

All 47 in 47 Tailwind CSS Toggles & Switches

  1. 01Tailwind iOS-Style Toggle Switch with appearance-none
  2. 02Tailwind Dark Mode Sun / Moon Theme Switcher
  3. 03Tailwind Neumorphic Soft-UI Pill Toggle
  4. 04Tailwind Gooey Liquid Morphing Switch with SVG Filter
  5. 05Tailwind Bouncy Elastic Physics Toggle (Overshoot Cubic-Bezier)
  6. 06Tailwind ON / OFF Text Switch with Labels Inside the Track
  7. 07Tailwind SaaS Pricing Toggle — Monthly / Annually Segmented Pill
  8. 08Tailwind Mechanical Keycap Toggles with Haptic Feedback
  9. 09Tailwind Waveform Mute Toggle with Animated EQ Bars
  10. 10Tailwind Timed Auto-Off Toggle with Conic Countdown Ring
  11. 11Tailwind Press-and-Hold to Confirm Toggle (Fingerprint Scan)
  12. 12Tailwind Split-Flap Departure Board Toggle (3D Flip)
  13. 13Tailwind Permission Toggle Group with :has() Live Status Badge
  14. 14Tailwind Material Design 3 Switch with State Layer
  15. 15Tailwind Skeuomorphic Wall Light Switch with Room Relight
  16. 16Tailwind Neon Cyberpunk Toggle with Clipped Corners
  17. 17Tailwind Brutalist Snap Toggle with Hard Offset Shadow
  18. 18Tailwind Day / Night Landscape Scene Switch
  19. 19Tailwind 3D Flip Card Toggle (rotateY Switch)
  20. 20Tailwind Sliding Pill Toggle with Label Inside the Thumb
  21. 21Tailwind Glassmorphism Frosted Toggle with backdrop-blur
  22. 22Tailwind Rotary Knob Toggle with Brushed-Metal Conic Gradient
  23. 23Tailwind iOS Settings List with Grouped Toggle Rows
  24. 24Tailwind Minimal Outline Toggle Drawn in currentColor
  25. 25Tailwind Squash-and-Stretch Pebble Toggle
  26. 26Tailwind Industrial Rocker I/O Power Switch
  27. 27Tailwind Accessible role=switch Toggle with forced-colors Support
  28. 28Tailwind Macro-Pad Toggle Grid with RGB Underglow
  29. 29Tailwind Radial VU Meter Mute Toggle
  30. 30Tailwind Segmented Pip Countdown Auto-Off Toggle
  31. 31Tailwind Slide-to-Confirm Toggle Rail (Drag or Arrow Keys)
  32. 32Tailwind Odometer Roll ON / OFF Toggle
  33. 33Tailwind Master Toggle with Indeterminate Tri-State
  34. 34Tailwind iOS Control Centre Tile Toggles
  35. 35Tailwind Material 3 Connected Icon Toggle Buttons
  36. 36Tailwind Two-Gang Wall Plate with Cumulative Room Light
  37. 37Tailwind Neon Toggle with Animated Conic Border Light
  38. 38Tailwind Brutalist Rubber-Stamp Toggle
  39. 39Tailwind Aurora Night Sky Toggle Switch
  40. 40Tailwind 3D Rotating Cube Toggle
  41. 41Tailwind Three-Position Off / Auto / On Selector
  42. 42Tailwind Liquid Glass Toggle with Specular Highlight
  43. 43Tailwind Three-Detent Rotary Selector Knob
  44. 44Tailwind Hardware Slide Switch with Knurled Grip
  45. 45Tailwind Battery Saver Toggle with Live Fill Meter
  46. 46Tailwind Morphing Sun-to-Moon Icon Toggle (SVG Stroke)
  47. 47Tailwind Guarded Kill Switch Under a Flip-Up Safety Cover
Pure TailwindMIT licensed

Tailwind Gooey Liquid Morphing Switch with SVG Filter

The blob that necks and snaps: an inline feGaussianBlur + feColorMatrix chain fuses a thumb with a trailing echo, and the only thing Tailwind has to do is move them — has-checked: on the track, a delay-[90ms] on the echo, and the filter draws the liquid between them.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="ttg-04 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-14 bg-ttg04-bg font-ttg04-sans" aria-label="Gooey liquid morphing switch">
  <svg width="0" height="0" aria-hidden="true" class="absolute">
    <defs><filter id="ttg04-goo"><feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b"/><feColorMatrix in="b" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7" result="g"/><feComposite in="SourceGraphic" in2="g" operator="atop"/></filter></defs>
  </svg>
  <label for="ttg-04-sw" class="flex items-center gap-8 px-7 py-5 rounded-3xl cursor-pointer bg-ttg04-card border border-ttg04-edge">
    <span class="flex flex-col gap-0.5">
      <span class="text-[15.5px] font-semibold text-ttg04-ink">Liquid mode</span>
      <span class="text-[12.5px] text-ttg04-mute">Metaball transitions between views</span>
    </span>
    <span class="relative shrink-0 w-24 h-12 rounded-[26px] bg-ttg04-track shadow-[inset_0_3px_8px_oklch(0.15_0.03_300/0.6)] has-checked:[&_.ttg04-blob]:translate-x-12 has-checked:[&_.ttg04-echo]:translate-x-12">
      <span aria-hidden="true" class="absolute inset-0 pointer-events-none [filter:url(#ttg04-goo)]">
        <i class="ttg04-blob absolute top-2 left-2 w-8 h-8 rounded-full bg-ttg04-goo transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] motion-reduce:duration-200"></i>
        <i class="ttg04-echo absolute top-2 left-2 w-8 h-8 rounded-full bg-ttg04-goo scale-[0.86] transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] delay-[90ms] motion-reduce:duration-200 motion-reduce:delay-0"></i>
      </span>
      <input type="checkbox" id="ttg-04-sw" role="switch" class="appearance-none absolute inset-0 w-full h-full rounded-[26px] bg-transparent cursor-pointer z-10 focus-visible:outline-3 focus-visible:outline-ttg04-goo focus-visible:outline-offset-4">
    </span>
  </label>
  <p class="font-ttg04-mono text-[11px] tracking-[0.14em] uppercase text-ttg04-mute">Watch the drop neck &amp; snap</p>
</section>
@import "tailwindcss";

@theme {
  --color-ttg04-bg: #16111f;
  --color-ttg04-bg: oklch(0.19 0.03 300);
  --color-ttg04-card: #1e1730;
  --color-ttg04-card: oklch(0.24 0.04 300);
  --color-ttg04-edge: #2f2547;
  --color-ttg04-edge: oklch(0.32 0.05 300);
  --color-ttg04-track: #241b38;
  --color-ttg04-track: oklch(0.27 0.05 300);
  --color-ttg04-goo: #e878b8;
  --color-ttg04-goo: oklch(0.72 0.19 340);
  --color-ttg04-ink: #ded5f0;
  --color-ttg04-ink: oklch(0.9 0.03 300);
  --color-ttg04-mute: #9b8fba;
  --color-ttg04-mute: oklch(0.68 0.05 300);
  --font-ttg04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-ttg04-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}

.ttg-04 {
  width: 100%;
  min-height: 100svh;
}

.ttg-04 input[type="checkbox"] {
  -webkit-appearance: none;
}
/* The scale on the echo blob must survive its translate; Tailwind composes both through
   its own transform variables, but the filtered layer is re-rasterised each frame in
   Safari, so pin it to its own compositing layer. */

.ttg-04 .ttg04-blob,
.ttg-04 .ttg04-echo {
  will-change: transform;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-ttg04-bg: #16111f;
  --color-ttg04-bg: oklch(0.19 0.03 300);
  --color-ttg04-card: #1e1730;
  --color-ttg04-card: oklch(0.24 0.04 300);
  --color-ttg04-edge: #2f2547;
  --color-ttg04-edge: oklch(0.32 0.05 300);
  --color-ttg04-track: #241b38;
  --color-ttg04-track: oklch(0.27 0.05 300);
  --color-ttg04-goo: #e878b8;
  --color-ttg04-goo: oklch(0.72 0.19 340);
  --color-ttg04-ink: #ded5f0;
  --color-ttg04-ink: oklch(0.9 0.03 300);
  --color-ttg04-mute: #9b8fba;
  --color-ttg04-mute: oklch(0.68 0.05 300);
  --font-ttg04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-ttg04-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}

.ttg-04 {
  width: 100%;
  min-height: 100svh;
}

.ttg-04 input[type="checkbox"] {
  -webkit-appearance: none;
}
/* The scale on the echo blob must survive its translate; Tailwind composes both through
   its own transform variables, but the filtered layer is re-rasterised each frame in
   Safari, so pin it to its own compositing layer. */

.ttg-04 .ttg04-blob,
.ttg-04 .ttg04-echo {
  will-change: transform;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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.

  • filter:url(#id) has no utility and should not have one — [filter:url(#ttg04-goo)] is the correct arbitrary property, and the namespaced id is what lets 47 demos share a page.
  • The checkbox is not a sibling of the blobs, so peer-checked: cannot reach them. has-checked:[&_.ttg04-blob]:translate-x-12 on the shared ancestor is the utility form of the original's .track:has(:checked) .blob — longer to type, identical output, and it sits on the element that owns both.
  • delay-[90ms] is the whole viscosity effect. Everything else about the goo is SVG.
  • UI change from the CSS original: the bare switch gained a bordered card and a supporting line, the background moved off pure #171222 to an oklch violet with a lighter card behind the track (the pink blob read as floating in void), and the caption ink lifted to oklch(0.68) for 4.6:1 — it was 3.1:1.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 Gooey Liquid Morphing Switch with SVG Filter
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-gooey-liquid-morphing-switch-svg-filter/

The blob that necks and snaps: an inline feGaussianBlur + feColorMatrix chain fuses a thumb with a trailing echo, and the only thing Tailwind has to do is move them &mdash; has-checked: on the track, a delay-[90ms] on the echo, and the filter draws the liquid between them.
## HTML
```html
<section class="ttg-04 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-14 bg-ttg04-bg font-ttg04-sans" aria-label="Gooey liquid morphing switch">
  <svg width="0" height="0" aria-hidden="true" class="absolute">
    <defs><filter id="ttg04-goo"><feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b"/><feColorMatrix in="b" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7" result="g"/><feComposite in="SourceGraphic" in2="g" operator="atop"/></filter></defs>
  </svg>
  <label for="ttg-04-sw" class="flex items-center gap-8 px-7 py-5 rounded-3xl cursor-pointer bg-ttg04-card border border-ttg04-edge">
    <span class="flex flex-col gap-0.5">
      <span class="text-[15.5px] font-semibold text-ttg04-ink">Liquid mode</span>
      <span class="text-[12.5px] text-ttg04-mute">Metaball transitions between views</span>
    </span>
    <span class="relative shrink-0 w-24 h-12 rounded-[26px] bg-ttg04-track shadow-[inset_0_3px_8px_oklch(0.15_0.03_300/0.6)] has-checked:[&_.ttg04-blob]:translate-x-12 has-checked:[&_.ttg04-echo]:translate-x-12">
      <span aria-hidden="true" class="absolute inset-0 pointer-events-none [filter:url(#ttg04-goo)]">
        <i class="ttg04-blob absolute top-2 left-2 w-8 h-8 rounded-full bg-ttg04-goo transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] motion-reduce:duration-200"></i>
        <i class="ttg04-echo absolute top-2 left-2 w-8 h-8 rounded-full bg-ttg04-goo scale-[0.86] transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] delay-[90ms] motion-reduce:duration-200 motion-reduce:delay-0"></i>
      </span>
      <input type="checkbox" id="ttg-04-sw" role="switch" class="appearance-none absolute inset-0 w-full h-full rounded-[26px] bg-transparent cursor-pointer z-10 focus-visible:outline-3 focus-visible:outline-ttg04-goo focus-visible:outline-offset-4">
    </span>
  </label>
  <p class="font-ttg04-mono text-[11px] tracking-[0.14em] uppercase text-ttg04-mute">Watch the drop neck &amp; snap</p>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-ttg04-bg: #16111f;
  --color-ttg04-bg: oklch(0.19 0.03 300);
  --color-ttg04-card: #1e1730;
  --color-ttg04-card: oklch(0.24 0.04 300);
  --color-ttg04-edge: #2f2547;
  --color-ttg04-edge: oklch(0.32 0.05 300);
  --color-ttg04-track: #241b38;
  --color-ttg04-track: oklch(0.27 0.05 300);
  --color-ttg04-goo: #e878b8;
  --color-ttg04-goo: oklch(0.72 0.19 340);
  --color-ttg04-ink: #ded5f0;
  --color-ttg04-ink: oklch(0.9 0.03 300);
  --color-ttg04-mute: #9b8fba;
  --color-ttg04-mute: oklch(0.68 0.05 300);
  --font-ttg04-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-ttg04-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}

.ttg-04 {
  width: 100%;
  min-height: 100svh;
}

.ttg-04 input[type="checkbox"] {
  -webkit-appearance: none;
}
/* The scale on the echo blob must survive its translate; Tailwind composes both through
   its own transform variables, but the filtered layer is re-rasterised each frame in
   Safari, so pin it to its own compositing layer. */

.ttg-04 .ttg04-blob,
.ttg-04 .ttg04-echo {
  will-change: transform;
}
```

How this works

The goo is SVG, and it should be. A container is filtered through filter:url(#ttg04-goo), whose chain blurs everything inside it (stdDeviation="6") then slams the alpha contrast back up (feColorMatrix 18 / −7). Any two shapes that overlap while blurred fuse into one silhouette. Tailwind has no utility for this and should not: the id is a document reference.

Two blobs, one delay. The thumb and its echo run the same 500ms transform transition, but the echo carries delay-[90ms]. Mid-travel they separate by just enough for the filter to draw a stretching neck; on arrival the echo catches up and the drop snaps whole.

The input floats above the goo. It is absolute inset-0 opacity-0-adjacent (transparent background, full size), so the whole track is clickable and focusable while the visual lives in the filtered layer beneath. Because the input is not a sibling of the blobs, the state has to travel through the ancestor: has-checked: on the track, with an arbitrary descendant selector.

Make it yours

  • More viscosity: raise the echo's delay-[90ms] toward delay-[120ms].
  • Runnier goo: stdDeviation="8" and rebalance the matrix to 20 / &minus;9.
  • Recolour through --color-ttg04-goo — the filter is colour-agnostic.
  • Droop at rest: give the echo a few px of translate-y in its base class list.

Gotchas — read before shipping

  • The filter must wrap ONLY the blobs. Filtering the whole switch blurs the track, the ring and the label with it.
  • Keep blob edges at least one blur radius away from the filtered container's bounds, or the goo clips to a flat edge.
  • Every instance needs a unique filter id. Two copies of #goo on one page silently both resolve to the first — which is exactly why every id here is namespaced ttg04-.
  • has-checked: plus an arbitrary descendant selector is doing real work here; if you support Firefox < 121, ship the plain sliding thumb as the fallback (the filter simply does nothing).

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

filter:url() with SVG filters is supported everywhere modern; :has() (Firefox 121+) drives the slide. Without either, the control still toggles and remains fully accessible.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…