30 / 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 Segmented Pip Countdown Auto-Off Toggle
The auto-off idea made countable: instead of a smooth ring, ten pips extinguish one per second, so the remaining time is a number you can read at a glance. Discrete beats read as urgency in a way a sweeping arc never does.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="ttg-30 w-full min-h-svh grid place-items-center px-5 py-14 bg-ttg30-bg font-ttg30-sans" aria-label="Segmented countdown auto-off toggle">
<div class="w-[min(360px,100%)] flex flex-col gap-5 px-7 py-6 rounded-[22px] bg-ttg30-card border border-ttg30-edge">
<div class="flex items-start justify-between gap-5">
<span class="flex flex-col gap-0.5">
<b class="text-[16px] font-bold text-ttg30-ink">Temporary admin</b>
<span class="text-[12.5px] text-ttg30-mute">Elevated rights, revoked automatically</span>
</span>
<input type="checkbox" id="ttg-30-sw" role="switch" aria-label="Temporary admin access" class="appearance-none shrink-0 w-14 h-8 rounded-full bg-ttg30-off relative cursor-pointer transition-colors duration-300 before:content-[''] before:absolute before:top-0.5 before:left-0.5 before:w-7 before:h-7 before:rounded-full before:bg-white before:shadow-[0_2px_6px_oklch(0_0_0/0.5)] before:transition-transform before:duration-300 before:ease-[cubic-bezier(0.25,0.9,0.3,1.1)] checked:bg-ttg30-warn checked:before:translate-x-6 focus-visible:outline-3 focus-visible:outline-ttg30-warn focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:before:transition-none">
</div>
<div id="ttg-30-pips" class="flex gap-1.5 data-counting:[&_.ttg30-pip]:[animation:ttg30-out_1s_steps(1,end)_calc(var(--i)*1s)_forwards]">
<i style="--i:0" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:1" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:2" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:3" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:4" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:5" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:6" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:7" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:8" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:9" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
</div>
<p id="ttg-30-live" aria-live="polite" class="font-ttg30-mono text-[12px] tracking-[0.08em] text-ttg30-mute">Off · grants 10s when enabled</p>
</div>
</section><section class="ttg-30 w-full min-h-svh grid place-items-center px-5 py-14 bg-ttg30-bg font-ttg30-sans" aria-label="Segmented countdown auto-off toggle">
<div class="w-[min(360px,100%)] flex flex-col gap-5 px-7 py-6 rounded-[22px] bg-ttg30-card border border-ttg30-edge">
<div class="flex items-start justify-between gap-5">
<span class="flex flex-col gap-0.5">
<b class="text-[16px] font-bold text-ttg30-ink">Temporary admin</b>
<span class="text-[12.5px] text-ttg30-mute">Elevated rights, revoked automatically</span>
</span>
<input type="checkbox" id="ttg-30-sw" role="switch" aria-label="Temporary admin access" class="appearance-none shrink-0 w-14 h-8 rounded-full bg-ttg30-off relative cursor-pointer transition-colors duration-300 before:content-[''] before:absolute before:top-0.5 before:left-0.5 before:w-7 before:h-7 before:rounded-full before:bg-white before:shadow-[0_2px_6px_oklch(0_0_0/0.5)] before:transition-transform before:duration-300 before:ease-[cubic-bezier(0.25,0.9,0.3,1.1)] checked:bg-ttg30-warn checked:before:translate-x-6 focus-visible:outline-3 focus-visible:outline-ttg30-warn focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:before:transition-none">
</div>
<div id="ttg-30-pips" class="flex gap-1.5 data-counting:[&_.ttg30-pip]:[animation:ttg30-out_1s_steps(1,end)_calc(var(--i)*1s)_forwards]">
<i style="--i:0" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:1" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:2" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:3" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:4" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:5" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:6" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:7" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:8" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:9" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
</div>
<p id="ttg-30-live" aria-live="polite" class="font-ttg30-mono text-[12px] tracking-[0.08em] text-ttg30-mute">Off · grants 10s when enabled</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-ttg30-bg: #121419;
--color-ttg30-bg: oklch(0.19 0.008 265);
--color-ttg30-card: #1a1e26;
--color-ttg30-card: oklch(0.24 0.01 265);
--color-ttg30-edge: #2a303b;
--color-ttg30-edge: oklch(0.31 0.012 265);
--color-ttg30-off: #343b48;
--color-ttg30-off: oklch(0.36 0.014 265);
--color-ttg30-pipoff: #333a47;
--color-ttg30-pipoff: oklch(0.35 0.014 265);
--color-ttg30-warn: #f0a63c;
--color-ttg30-warn: oklch(0.78 0.15 68);
--color-ttg30-ink: #e7eaf2;
--color-ttg30-ink: oklch(0.93 0.008 265);
--color-ttg30-mute: #939bad;
--color-ttg30-mute: oklch(0.68 0.015 265);
--font-ttg30-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg30-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
/* Pips start lit when armed and snap out one per second; steps(1) because a pip is a
discrete unit of time, not a fade. */
@keyframes ttg30-out {
0% {
background-color: var(--color-ttg30-warn);
}
100% {
background-color: var(--color-ttg30-pipoff);
}
}
.ttg-30 {
width: 100%;
min-height: 100svh;
}
.ttg-30 input[type="checkbox"] {
-webkit-appearance: none;
}
/* Armed but not yet extinguished: every pip is lit until its delay elapses. */
.ttg-30 [data-counting] .ttg30-pip {
background-color: var(--color-ttg30-warn);
}@import "tailwindcss";
@theme {
--color-ttg30-bg: #121419;
--color-ttg30-bg: oklch(0.19 0.008 265);
--color-ttg30-card: #1a1e26;
--color-ttg30-card: oklch(0.24 0.01 265);
--color-ttg30-edge: #2a303b;
--color-ttg30-edge: oklch(0.31 0.012 265);
--color-ttg30-off: #343b48;
--color-ttg30-off: oklch(0.36 0.014 265);
--color-ttg30-pipoff: #333a47;
--color-ttg30-pipoff: oklch(0.35 0.014 265);
--color-ttg30-warn: #f0a63c;
--color-ttg30-warn: oklch(0.78 0.15 68);
--color-ttg30-ink: #e7eaf2;
--color-ttg30-ink: oklch(0.93 0.008 265);
--color-ttg30-mute: #939bad;
--color-ttg30-mute: oklch(0.68 0.015 265);
--font-ttg30-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg30-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
/* Pips start lit when armed and snap out one per second; steps(1) because a pip is a
discrete unit of time, not a fade. */
@keyframes ttg30-out {
0% {
background-color: var(--color-ttg30-warn);
}
100% {
background-color: var(--color-ttg30-pipoff);
}
}
.ttg-30 {
width: 100%;
min-height: 100svh;
}
.ttg-30 input[type="checkbox"] {
-webkit-appearance: none;
}
/* Armed but not yet extinguished: every pip is lit until its delay elapses. */
.ttg-30 [data-counting] .ttg30-pip {
background-color: var(--color-ttg30-warn);
}(() => {
const TOTAL = 10;
const sw = document.querySelector('#ttg-30-sw');
const pips = document.querySelector('#ttg-30-pips');
const live = document.querySelector('#ttg-30-live');
if (!sw || !pips || !live) return;
let tick = null, end = null;
const stop = (msg) => {
clearInterval(tick); tick = null;
pips.removeAttribute('data-counting');
live.textContent = msg;
};
sw.addEventListener('change', () => {
clearInterval(tick);
if (!sw.checked) { stop('Off \u00b7 grants 10s when enabled'); return; }
pips.removeAttribute('data-counting');
void pips.offsetWidth; // restart the staggered pip animations
pips.setAttribute('data-counting', '');
end = Date.now() + TOTAL * 1000;
live.textContent = 'Admin access \u2014 ' + TOTAL + 's remaining';
tick = setInterval(() => {
const left = Math.max(0, Math.round((end - Date.now()) / 1000));
if (left <= 0) { sw.checked = false; stop('Expired \u2014 access revoked'); return; }
live.textContent = 'Admin access \u2014 ' + left + 's remaining';
}, 1000);
});
})();(() => {
const TOTAL = 10;
const sw = document.querySelector('#ttg-30-sw');
const pips = document.querySelector('#ttg-30-pips');
const live = document.querySelector('#ttg-30-live');
if (!sw || !pips || !live) return;
let tick = null, end = null;
const stop = (msg) => {
clearInterval(tick); tick = null;
pips.removeAttribute('data-counting');
live.textContent = msg;
};
sw.addEventListener('change', () => {
clearInterval(tick);
if (!sw.checked) { stop('Off \u00b7 grants 10s when enabled'); return; }
pips.removeAttribute('data-counting');
void pips.offsetWidth; // restart the staggered pip animations
pips.setAttribute('data-counting', '');
end = Date.now() + TOTAL * 1000;
live.textContent = 'Admin access \u2014 ' + TOTAL + 's remaining';
tick = setInterval(() => {
const left = Math.max(0, Math.round((end - Date.now()) / 1000));
if (left <= 0) { sw.checked = false; stop('Expired \u2014 access revoked'); return; }
live.textContent = 'Admin access \u2014 ' + left + 's remaining';
}, 1000);
});
})();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-ttg30-bg: #121419;
--color-ttg30-bg: oklch(0.19 0.008 265);
--color-ttg30-card: #1a1e26;
--color-ttg30-card: oklch(0.24 0.01 265);
--color-ttg30-edge: #2a303b;
--color-ttg30-edge: oklch(0.31 0.012 265);
--color-ttg30-off: #343b48;
--color-ttg30-off: oklch(0.36 0.014 265);
--color-ttg30-pipoff: #333a47;
--color-ttg30-pipoff: oklch(0.35 0.014 265);
--color-ttg30-warn: #f0a63c;
--color-ttg30-warn: oklch(0.78 0.15 68);
--color-ttg30-ink: #e7eaf2;
--color-ttg30-ink: oklch(0.93 0.008 265);
--color-ttg30-mute: #939bad;
--color-ttg30-mute: oklch(0.68 0.015 265);
--font-ttg30-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg30-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
/* Pips start lit when armed and snap out one per second; steps(1) because a pip is a
discrete unit of time, not a fade. */
@keyframes ttg30-out {
0% {
background-color: var(--color-ttg30-warn);
}
100% {
background-color: var(--color-ttg30-pipoff);
}
}
.ttg-30 {
width: 100%;
min-height: 100svh;
}
.ttg-30 input[type="checkbox"] {
-webkit-appearance: none;
}
/* Armed but not yet extinguished: every pip is lit until its delay elapses. */
.ttg-30 [data-counting] .ttg30-pip {
background-color: var(--color-ttg30-warn);
}@import "tailwindcss";
@theme {
--color-ttg30-bg: #121419;
--color-ttg30-bg: oklch(0.19 0.008 265);
--color-ttg30-card: #1a1e26;
--color-ttg30-card: oklch(0.24 0.01 265);
--color-ttg30-edge: #2a303b;
--color-ttg30-edge: oklch(0.31 0.012 265);
--color-ttg30-off: #343b48;
--color-ttg30-off: oklch(0.36 0.014 265);
--color-ttg30-pipoff: #333a47;
--color-ttg30-pipoff: oklch(0.35 0.014 265);
--color-ttg30-warn: #f0a63c;
--color-ttg30-warn: oklch(0.78 0.15 68);
--color-ttg30-ink: #e7eaf2;
--color-ttg30-ink: oklch(0.93 0.008 265);
--color-ttg30-mute: #939bad;
--color-ttg30-mute: oklch(0.68 0.015 265);
--font-ttg30-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg30-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
/* Pips start lit when armed and snap out one per second; steps(1) because a pip is a
discrete unit of time, not a fade. */
@keyframes ttg30-out {
0% {
background-color: var(--color-ttg30-warn);
}
100% {
background-color: var(--color-ttg30-pipoff);
}
}
.ttg-30 {
width: 100%;
min-height: 100svh;
}
.ttg-30 input[type="checkbox"] {
-webkit-appearance: none;
}
/* Armed but not yet extinguished: every pip is lit until its delay elapses. */
.ttg-30 [data-counting] .ttg30-pip {
background-color: var(--color-ttg30-warn);
}<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;600&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;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
[animation:ttg30-out_1s_steps(1,end)_calc(var(--i)*1s)_forwards]on ten identical pips is the entire clock — no per-pip class and no generated CSS per index. The delay has to live INSIDE the shorthand: ananimate-*utility (or anyanimation:shorthand) applied from a variant resetsanimation-delay, so a separate[animation-delay:…]on the pip is discarded and all ten extinguish at once.steps(1,end)rather than a fade: a pip is a unit of time, and a half-faded pip means nothing. Same reasoning as demo 17's snap, applied to a countdown.- Two scoped CSS rules do what a variant cannot: light every pip when armed, and keep the extinguished state after the animation ends. Both are about the ARMED container, not the pip's own state.
- UI upgrade over demo 10: remaining time is now countable at a glance and spoken as a number every second, the resting copy explains the grant before you flip it, and the card is a settings row rather than a lone dial — which is the context this control actually ships in.
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 Segmented Pip Countdown Auto-Off Toggle
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-segmented-pip-countdown-auto-off-toggle/
The auto-off idea made countable: instead of a smooth ring, ten pips extinguish one per second, so the remaining time is a number you can read at a glance. Discrete beats read as urgency in a way a sweeping arc never does.
## HTML
```html
<section class="ttg-30 w-full min-h-svh grid place-items-center px-5 py-14 bg-ttg30-bg font-ttg30-sans" aria-label="Segmented countdown auto-off toggle">
<div class="w-[min(360px,100%)] flex flex-col gap-5 px-7 py-6 rounded-[22px] bg-ttg30-card border border-ttg30-edge">
<div class="flex items-start justify-between gap-5">
<span class="flex flex-col gap-0.5">
<b class="text-[16px] font-bold text-ttg30-ink">Temporary admin</b>
<span class="text-[12.5px] text-ttg30-mute">Elevated rights, revoked automatically</span>
</span>
<input type="checkbox" id="ttg-30-sw" role="switch" aria-label="Temporary admin access" class="appearance-none shrink-0 w-14 h-8 rounded-full bg-ttg30-off relative cursor-pointer transition-colors duration-300 before:content-[''] before:absolute before:top-0.5 before:left-0.5 before:w-7 before:h-7 before:rounded-full before:bg-white before:shadow-[0_2px_6px_oklch(0_0_0/0.5)] before:transition-transform before:duration-300 before:ease-[cubic-bezier(0.25,0.9,0.3,1.1)] checked:bg-ttg30-warn checked:before:translate-x-6 focus-visible:outline-3 focus-visible:outline-ttg30-warn focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:before:transition-none">
</div>
<div id="ttg-30-pips" class="flex gap-1.5 data-counting:[&_.ttg30-pip]:[animation:ttg30-out_1s_steps(1,end)_calc(var(--i)*1s)_forwards]">
<i style="--i:0" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:1" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:2" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:3" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:4" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:5" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:6" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:7" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:8" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:9" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
</div>
<p id="ttg-30-live" aria-live="polite" class="font-ttg30-mono text-[12px] tracking-[0.08em] text-ttg30-mute">Off · grants 10s when enabled</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg30-bg: #121419;
--color-ttg30-bg: oklch(0.19 0.008 265);
--color-ttg30-card: #1a1e26;
--color-ttg30-card: oklch(0.24 0.01 265);
--color-ttg30-edge: #2a303b;
--color-ttg30-edge: oklch(0.31 0.012 265);
--color-ttg30-off: #343b48;
--color-ttg30-off: oklch(0.36 0.014 265);
--color-ttg30-pipoff: #333a47;
--color-ttg30-pipoff: oklch(0.35 0.014 265);
--color-ttg30-warn: #f0a63c;
--color-ttg30-warn: oklch(0.78 0.15 68);
--color-ttg30-ink: #e7eaf2;
--color-ttg30-ink: oklch(0.93 0.008 265);
--color-ttg30-mute: #939bad;
--color-ttg30-mute: oklch(0.68 0.015 265);
--font-ttg30-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg30-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
/* Pips start lit when armed and snap out one per second; steps(1) because a pip is a
discrete unit of time, not a fade. */
@keyframes ttg30-out {
0% {
background-color: var(--color-ttg30-warn);
}
100% {
background-color: var(--color-ttg30-pipoff);
}
}
.ttg-30 {
width: 100%;
min-height: 100svh;
}
.ttg-30 input[type="checkbox"] {
-webkit-appearance: none;
}
/* Armed but not yet extinguished: every pip is lit until its delay elapses. */
.ttg-30 [data-counting] .ttg30-pip {
background-color: var(--color-ttg30-warn);
}
```
## JavaScript
```js
(() => {
const TOTAL = 10;
const sw = document.querySelector('#ttg-30-sw');
const pips = document.querySelector('#ttg-30-pips');
const live = document.querySelector('#ttg-30-live');
if (!sw || !pips || !live) return;
let tick = null, end = null;
const stop = (msg) => {
clearInterval(tick); tick = null;
pips.removeAttribute('data-counting');
live.textContent = msg;
};
sw.addEventListener('change', () => {
clearInterval(tick);
if (!sw.checked) { stop('Off \u00b7 grants 10s when enabled'); return; }
pips.removeAttribute('data-counting');
void pips.offsetWidth; // restart the staggered pip animations
pips.setAttribute('data-counting', '');
end = Date.now() + TOTAL * 1000;
live.textContent = 'Admin access \u2014 ' + TOTAL + 's remaining';
tick = setInterval(() => {
const left = Math.max(0, Math.round((end - Date.now()) / 1000));
if (left <= 0) { sw.checked = false; stop('Expired \u2014 access revoked'); return; }
live.textContent = 'Admin access \u2014 ' + left + 's remaining';
}, 1000);
});
})();
```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 Segmented Pip Countdown Auto-Off Toggle
Source: https://codefronts.com/components/tailwind-css-toggles-switches/tailwind-segmented-pip-countdown-auto-off-toggle/
The auto-off idea made countable: instead of a smooth ring, ten pips extinguish one per second, so the remaining time is a number you can read at a glance. Discrete beats read as urgency in a way a sweeping arc never does.
## HTML
```html
<section class="ttg-30 w-full min-h-svh grid place-items-center px-5 py-14 bg-ttg30-bg font-ttg30-sans" aria-label="Segmented countdown auto-off toggle">
<div class="w-[min(360px,100%)] flex flex-col gap-5 px-7 py-6 rounded-[22px] bg-ttg30-card border border-ttg30-edge">
<div class="flex items-start justify-between gap-5">
<span class="flex flex-col gap-0.5">
<b class="text-[16px] font-bold text-ttg30-ink">Temporary admin</b>
<span class="text-[12.5px] text-ttg30-mute">Elevated rights, revoked automatically</span>
</span>
<input type="checkbox" id="ttg-30-sw" role="switch" aria-label="Temporary admin access" class="appearance-none shrink-0 w-14 h-8 rounded-full bg-ttg30-off relative cursor-pointer transition-colors duration-300 before:content-[''] before:absolute before:top-0.5 before:left-0.5 before:w-7 before:h-7 before:rounded-full before:bg-white before:shadow-[0_2px_6px_oklch(0_0_0/0.5)] before:transition-transform before:duration-300 before:ease-[cubic-bezier(0.25,0.9,0.3,1.1)] checked:bg-ttg30-warn checked:before:translate-x-6 focus-visible:outline-3 focus-visible:outline-ttg30-warn focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:before:transition-none">
</div>
<div id="ttg-30-pips" class="flex gap-1.5 data-counting:[&_.ttg30-pip]:[animation:ttg30-out_1s_steps(1,end)_calc(var(--i)*1s)_forwards]">
<i style="--i:0" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:1" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:2" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:3" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:4" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:5" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:6" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:7" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:8" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
<i style="--i:9" class="ttg30-pip h-1.5 flex-1 rounded-full bg-ttg30-pipoff"></i>
</div>
<p id="ttg-30-live" aria-live="polite" class="font-ttg30-mono text-[12px] tracking-[0.08em] text-ttg30-mute">Off · grants 10s when enabled</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttg30-bg: #121419;
--color-ttg30-bg: oklch(0.19 0.008 265);
--color-ttg30-card: #1a1e26;
--color-ttg30-card: oklch(0.24 0.01 265);
--color-ttg30-edge: #2a303b;
--color-ttg30-edge: oklch(0.31 0.012 265);
--color-ttg30-off: #343b48;
--color-ttg30-off: oklch(0.36 0.014 265);
--color-ttg30-pipoff: #333a47;
--color-ttg30-pipoff: oklch(0.35 0.014 265);
--color-ttg30-warn: #f0a63c;
--color-ttg30-warn: oklch(0.78 0.15 68);
--color-ttg30-ink: #e7eaf2;
--color-ttg30-ink: oklch(0.93 0.008 265);
--color-ttg30-mute: #939bad;
--color-ttg30-mute: oklch(0.68 0.015 265);
--font-ttg30-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-ttg30-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
/* Pips start lit when armed and snap out one per second; steps(1) because a pip is a
discrete unit of time, not a fade. */
@keyframes ttg30-out {
0% {
background-color: var(--color-ttg30-warn);
}
100% {
background-color: var(--color-ttg30-pipoff);
}
}
.ttg-30 {
width: 100%;
min-height: 100svh;
}
.ttg-30 input[type="checkbox"] {
-webkit-appearance: none;
}
/* Armed but not yet extinguished: every pip is lit until its delay elapses. */
.ttg-30 [data-counting] .ttg30-pip {
background-color: var(--color-ttg30-warn);
}
```
## JavaScript
```js
(() => {
const TOTAL = 10;
const sw = document.querySelector('#ttg-30-sw');
const pips = document.querySelector('#ttg-30-pips');
const live = document.querySelector('#ttg-30-live');
if (!sw || !pips || !live) return;
let tick = null, end = null;
const stop = (msg) => {
clearInterval(tick); tick = null;
pips.removeAttribute('data-counting');
live.textContent = msg;
};
sw.addEventListener('change', () => {
clearInterval(tick);
if (!sw.checked) { stop('Off \u00b7 grants 10s when enabled'); return; }
pips.removeAttribute('data-counting');
void pips.offsetWidth; // restart the staggered pip animations
pips.setAttribute('data-counting', '');
end = Date.now() + TOTAL * 1000;
live.textContent = 'Admin access \u2014 ' + TOTAL + 's remaining';
tick = setInterval(() => {
const left = Math.max(0, Math.round((end - Date.now()) / 1000));
if (left <= 0) { sw.checked = false; stop('Expired \u2014 access revoked'); return; }
live.textContent = 'Admin access \u2014 ' + left + 's remaining';
}, 1000);
});
})();
```How this works
The stagger is the countdown. Each pip has an index in a custom property and an animation delay of that many seconds. Arm the row and pip 0 goes out immediately, pip 1 a second later, and so on — ten CSS animations replacing a per-frame progress calculation.
JavaScript owns the truth, as always. A one-second interval updates the readout and a single timeout flips the switch off at ten. The pips are illustration; if the tab sleeps, the timeout still lands and the pips are re-armed from the real state.
Discrete beats communicate differently. A draining ring says "time is passing"; ten pips say "you have four left". For a permission that will revoke itself, countable is the better read.
Make it yours
- Change the window by adding or removing pips and matching
DURATION— one pip is one second by construction. - Urgency: give the last three pips a red token and a faster pulse.
- Extend-on-interaction: reset the attribute and the interval on any activity inside the card.
- Swap pips for a segmented bar by widening them and removing the gap.
Gotchas — read before shipping
- Re-arming needs the reflow trick (remove attribute, read
offsetWidth, re-add) or the animations do not restart. - Never pair an
animate-*(or shorthand[animation:…]) variant with a separate[animation-delay:…]utility — the shorthand resets the longhand, and the variant's higher specificity means it wins. - Keep pip count and duration in sync in exactly one place, or the last pip goes out before or after the switch flips — the most visible possible bug in a countdown.
- The readout must come from the interval, never from animation progress.
- Ten simultaneous animations are trivial, but ten
setTimeouts would not be — one interval, one timeout.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Keyframes with per-element delays are universal; nothing here needs @property or :has().