47 CSS Toggles & Switches10 / 47
Timed Auto-Off Toggle
A self-disarming switch for risky settings: flip it on and a conic countdown ring drains around the control for 8 seconds, then the toggle flips itself back off — with the remaining time announced to screen readers. Think 'temporarily allow' permissions.
Published
The code
<section class="tgl-10" aria-label="Timed auto-off toggle">
<div class="tgl-10__card">
<div class="tgl-10__head"><b>Temporarily allow</b><span>USB debugging · auto-off in 8s</span></div>
<span class="tgl-10__ringwrap" id="tgl-10-ring">
<input type="checkbox" id="tgl-10-sw" class="tgl-10__sw" role="switch" aria-label="Temporarily allow USB debugging">
</span>
<p class="tgl-10__live" id="tgl-10-live" aria-live="polite"></p>
</div>
</section><section class="tgl-10" aria-label="Timed auto-off toggle">
<div class="tgl-10__card">
<div class="tgl-10__head"><b>Temporarily allow</b><span>USB debugging · auto-off in 8s</span></div>
<span class="tgl-10__ringwrap" id="tgl-10-ring">
<input type="checkbox" id="tgl-10-sw" class="tgl-10__sw" role="switch" aria-label="Temporarily allow USB debugging">
</span>
<p class="tgl-10__live" id="tgl-10-live" aria-live="polite"></p>
</div>
</section>@property --tgl-10-p {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
.tgl-10,
.tgl-10 *,
.tgl-10 *::before,
.tgl-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tgl-10 {
--warn: oklch(0.72 0.16 60);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: #15171d;
}
.tgl-10__card {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
background: #1d2028;
border: 1px solid #2a2e3a;
border-radius: 20px;
padding: 30px 36px;
width: min(340px,100%);
}
.tgl-10__head {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
text-align: center;
}
.tgl-10__head b {
font-size: 17px;
color: #e8eaf2;
}
.tgl-10__head span {
font-size: 12.5px;
color: #7d8494;
}
.tgl-10__ringwrap {
--dur: 8s;
position: relative;
display: grid;
place-items: center;
width: 110px;
height: 110px;
border-radius: 50%;
background: conic-gradient(var(--warn) var(--tgl-10-p),#2a2e3a 0);
}
.tgl-10__ringwrap::before {
content: "";
position: absolute;
inset: 5px;
border-radius: 50%;
background: #1d2028;
}
.tgl-10__ringwrap.tgl-10--counting {
animation: tgl-10-drain var(--dur) linear forwards;
}
@keyframes tgl-10-drain {
from {
--tgl-10-p: 100%;
}
to {
--tgl-10-p: 0%;
}
}
.tgl-10__sw {
appearance: none;
-webkit-appearance: none;
position: relative;
z-index: 1;
width: 64px;
height: 36px;
border-radius: 20px;
background: #2c3040;
cursor: pointer;
transition: background-color .3s;
}
.tgl-10__sw:checked {
background: var(--warn);
}
.tgl-10__sw::before {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 6px rgba(0,0,0,.5);
transition: transform .3s cubic-bezier(.25,.9,.3,1.1);
}
.tgl-10__sw:checked::before {
transform: translateX(28px);
}
.tgl-10__sw:focus-visible {
outline: 3px solid var(--warn);
outline-offset: 4px;
}
.tgl-10__live {
font-size: 12.5px;
color: #8f96a8;
min-height: 1.2em;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.tgl-10__sw,
.tgl-10__sw::before {
transition: none;
}
}@property --tgl-10-p {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
.tgl-10,
.tgl-10 *,
.tgl-10 *::before,
.tgl-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tgl-10 {
--warn: oklch(0.72 0.16 60);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: #15171d;
}
.tgl-10__card {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
background: #1d2028;
border: 1px solid #2a2e3a;
border-radius: 20px;
padding: 30px 36px;
width: min(340px,100%);
}
.tgl-10__head {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
text-align: center;
}
.tgl-10__head b {
font-size: 17px;
color: #e8eaf2;
}
.tgl-10__head span {
font-size: 12.5px;
color: #7d8494;
}
.tgl-10__ringwrap {
--dur: 8s;
position: relative;
display: grid;
place-items: center;
width: 110px;
height: 110px;
border-radius: 50%;
background: conic-gradient(var(--warn) var(--tgl-10-p),#2a2e3a 0);
}
.tgl-10__ringwrap::before {
content: "";
position: absolute;
inset: 5px;
border-radius: 50%;
background: #1d2028;
}
.tgl-10__ringwrap.tgl-10--counting {
animation: tgl-10-drain var(--dur) linear forwards;
}
@keyframes tgl-10-drain {
from {
--tgl-10-p: 100%;
}
to {
--tgl-10-p: 0%;
}
}
.tgl-10__sw {
appearance: none;
-webkit-appearance: none;
position: relative;
z-index: 1;
width: 64px;
height: 36px;
border-radius: 20px;
background: #2c3040;
cursor: pointer;
transition: background-color .3s;
}
.tgl-10__sw:checked {
background: var(--warn);
}
.tgl-10__sw::before {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 6px rgba(0,0,0,.5);
transition: transform .3s cubic-bezier(.25,.9,.3,1.1);
}
.tgl-10__sw:checked::before {
transform: translateX(28px);
}
.tgl-10__sw:focus-visible {
outline: 3px solid var(--warn);
outline-offset: 4px;
}
.tgl-10__live {
font-size: 12.5px;
color: #8f96a8;
min-height: 1.2em;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.tgl-10__sw,
.tgl-10__sw::before {
transition: none;
}
}(() => {
const DURATION = 8000;
const sw = document.querySelector('#tgl-10-sw');
const ring = document.querySelector('#tgl-10-ring');
const live = document.querySelector('#tgl-10-live');
let timer = null;
sw.addEventListener('change', () => {
clearTimeout(timer);
if (sw.checked) {
ring.classList.remove('tgl-10--counting');
void ring.offsetWidth; // restart the CSS countdown animation
ring.classList.add('tgl-10--counting');
live.textContent = 'Enabled for 8 seconds';
timer = setTimeout(() => {
sw.checked = false;
ring.classList.remove('tgl-10--counting');
live.textContent = 'Auto-disabled';
}, DURATION);
} else {
ring.classList.remove('tgl-10--counting');
live.textContent = 'Disabled';
}
});
})();(() => {
const DURATION = 8000;
const sw = document.querySelector('#tgl-10-sw');
const ring = document.querySelector('#tgl-10-ring');
const live = document.querySelector('#tgl-10-live');
let timer = null;
sw.addEventListener('change', () => {
clearTimeout(timer);
if (sw.checked) {
ring.classList.remove('tgl-10--counting');
void ring.offsetWidth; // restart the CSS countdown animation
ring.classList.add('tgl-10--counting');
live.textContent = 'Enabled for 8 seconds';
timer = setTimeout(() => {
sw.checked = false;
ring.classList.remove('tgl-10--counting');
live.textContent = 'Auto-disabled';
}, DURATION);
} else {
ring.classList.remove('tgl-10--counting');
live.textContent = 'Disabled';
}
});
})();Here's a working CSS Toggles & Switche 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: Timed Auto-Off Toggle
Source: https://codefronts.com/components/css-toggles-switches/timed-auto-off-toggle/
A self-disarming switch for risky settings: flip it on and a conic countdown ring drains around the control for 8 seconds, then the toggle flips itself back off — with the remaining time announced to screen readers. Think 'temporarily allow' permissions.
## HTML
```html
<section class="tgl-10" aria-label="Timed auto-off toggle">
<div class="tgl-10__card">
<div class="tgl-10__head"><b>Temporarily allow</b><span>USB debugging · auto-off in 8s</span></div>
<span class="tgl-10__ringwrap" id="tgl-10-ring">
<input type="checkbox" id="tgl-10-sw" class="tgl-10__sw" role="switch" aria-label="Temporarily allow USB debugging">
</span>
<p class="tgl-10__live" id="tgl-10-live" aria-live="polite"></p>
</div>
</section>
```
## CSS
```css
@property --tgl-10-p {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
.tgl-10,
.tgl-10 *,
.tgl-10 *::before,
.tgl-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tgl-10 {
--warn: oklch(0.72 0.16 60);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: #15171d;
}
.tgl-10__card {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
background: #1d2028;
border: 1px solid #2a2e3a;
border-radius: 20px;
padding: 30px 36px;
width: min(340px,100%);
}
.tgl-10__head {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
text-align: center;
}
.tgl-10__head b {
font-size: 17px;
color: #e8eaf2;
}
.tgl-10__head span {
font-size: 12.5px;
color: #7d8494;
}
.tgl-10__ringwrap {
--dur: 8s;
position: relative;
display: grid;
place-items: center;
width: 110px;
height: 110px;
border-radius: 50%;
background: conic-gradient(var(--warn) var(--tgl-10-p),#2a2e3a 0);
}
.tgl-10__ringwrap::before {
content: "";
position: absolute;
inset: 5px;
border-radius: 50%;
background: #1d2028;
}
.tgl-10__ringwrap.tgl-10--counting {
animation: tgl-10-drain var(--dur) linear forwards;
}
@keyframes tgl-10-drain {
from {
--tgl-10-p: 100%;
}
to {
--tgl-10-p: 0%;
}
}
.tgl-10__sw {
appearance: none;
-webkit-appearance: none;
position: relative;
z-index: 1;
width: 64px;
height: 36px;
border-radius: 20px;
background: #2c3040;
cursor: pointer;
transition: background-color .3s;
}
.tgl-10__sw:checked {
background: var(--warn);
}
.tgl-10__sw::before {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 6px rgba(0,0,0,.5);
transition: transform .3s cubic-bezier(.25,.9,.3,1.1);
}
.tgl-10__sw:checked::before {
transform: translateX(28px);
}
.tgl-10__sw:focus-visible {
outline: 3px solid var(--warn);
outline-offset: 4px;
}
.tgl-10__live {
font-size: 12.5px;
color: #8f96a8;
min-height: 1.2em;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.tgl-10__sw,
.tgl-10__sw::before {
transition: none;
}
}
```
## JavaScript
```js
(() => {
const DURATION = 8000;
const sw = document.querySelector('#tgl-10-sw');
const ring = document.querySelector('#tgl-10-ring');
const live = document.querySelector('#tgl-10-live');
let timer = null;
sw.addEventListener('change', () => {
clearTimeout(timer);
if (sw.checked) {
ring.classList.remove('tgl-10--counting');
void ring.offsetWidth; // restart the CSS countdown animation
ring.classList.add('tgl-10--counting');
live.textContent = 'Enabled for 8 seconds';
timer = setTimeout(() => {
sw.checked = false;
ring.classList.remove('tgl-10--counting');
live.textContent = 'Auto-disabled';
}, DURATION);
} else {
ring.classList.remove('tgl-10--counting');
live.textContent = 'Disabled';
}
});
})();
```Here's a working CSS Toggles & Switche 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: Timed Auto-Off Toggle
Source: https://codefronts.com/components/css-toggles-switches/timed-auto-off-toggle/
A self-disarming switch for risky settings: flip it on and a conic countdown ring drains around the control for 8 seconds, then the toggle flips itself back off — with the remaining time announced to screen readers. Think 'temporarily allow' permissions.
## HTML
```html
<section class="tgl-10" aria-label="Timed auto-off toggle">
<div class="tgl-10__card">
<div class="tgl-10__head"><b>Temporarily allow</b><span>USB debugging · auto-off in 8s</span></div>
<span class="tgl-10__ringwrap" id="tgl-10-ring">
<input type="checkbox" id="tgl-10-sw" class="tgl-10__sw" role="switch" aria-label="Temporarily allow USB debugging">
</span>
<p class="tgl-10__live" id="tgl-10-live" aria-live="polite"></p>
</div>
</section>
```
## CSS
```css
@property --tgl-10-p {
syntax: '<percentage>';
inherits: false;
initial-value: 100%;
}
.tgl-10,
.tgl-10 *,
.tgl-10 *::before,
.tgl-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tgl-10 {
--warn: oklch(0.72 0.16 60);
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: #15171d;
}
.tgl-10__card {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
background: #1d2028;
border: 1px solid #2a2e3a;
border-radius: 20px;
padding: 30px 36px;
width: min(340px,100%);
}
.tgl-10__head {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
text-align: center;
}
.tgl-10__head b {
font-size: 17px;
color: #e8eaf2;
}
.tgl-10__head span {
font-size: 12.5px;
color: #7d8494;
}
.tgl-10__ringwrap {
--dur: 8s;
position: relative;
display: grid;
place-items: center;
width: 110px;
height: 110px;
border-radius: 50%;
background: conic-gradient(var(--warn) var(--tgl-10-p),#2a2e3a 0);
}
.tgl-10__ringwrap::before {
content: "";
position: absolute;
inset: 5px;
border-radius: 50%;
background: #1d2028;
}
.tgl-10__ringwrap.tgl-10--counting {
animation: tgl-10-drain var(--dur) linear forwards;
}
@keyframes tgl-10-drain {
from {
--tgl-10-p: 100%;
}
to {
--tgl-10-p: 0%;
}
}
.tgl-10__sw {
appearance: none;
-webkit-appearance: none;
position: relative;
z-index: 1;
width: 64px;
height: 36px;
border-radius: 20px;
background: #2c3040;
cursor: pointer;
transition: background-color .3s;
}
.tgl-10__sw:checked {
background: var(--warn);
}
.tgl-10__sw::before {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 6px rgba(0,0,0,.5);
transition: transform .3s cubic-bezier(.25,.9,.3,1.1);
}
.tgl-10__sw:checked::before {
transform: translateX(28px);
}
.tgl-10__sw:focus-visible {
outline: 3px solid var(--warn);
outline-offset: 4px;
}
.tgl-10__live {
font-size: 12.5px;
color: #8f96a8;
min-height: 1.2em;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.tgl-10__sw,
.tgl-10__sw::before {
transition: none;
}
}
```
## JavaScript
```js
(() => {
const DURATION = 8000;
const sw = document.querySelector('#tgl-10-sw');
const ring = document.querySelector('#tgl-10-ring');
const live = document.querySelector('#tgl-10-live');
let timer = null;
sw.addEventListener('change', () => {
clearTimeout(timer);
if (sw.checked) {
ring.classList.remove('tgl-10--counting');
void ring.offsetWidth; // restart the CSS countdown animation
ring.classList.add('tgl-10--counting');
live.textContent = 'Enabled for 8 seconds';
timer = setTimeout(() => {
sw.checked = false;
ring.classList.remove('tgl-10--counting');
live.textContent = 'Auto-disabled';
}, DURATION);
} else {
ring.classList.remove('tgl-10--counting');
live.textContent = 'Disabled';
}
});
})();
```How this works
The countdown ring is a registered custom property: @property --tgl-10-p declares an interpolable percentage, and a conic-gradient border uses it as the sweep stop. Checking the input adds a class that runs a linear 8s keyframe animating --tgl-10-p 100→0 — the browser tweens the gradient natively, no rAF loop.
JavaScript owns only the state machine: on check it (re)starts the CSS animation and an 8s setTimeout; on expiry it unchecks the input, and a polite aria-live region announces 'enabled for 8 seconds' / 'auto-disabled'. Unchecking early cancels the timer. If the tab sleeps, the timeout still fires on wake, so state can't stick on.
Make it yours
- Change the window by editing one constant (DURATION) — the CSS reads it from a --dur variable.
- Make expiry sticky-warn: flash the ring red for the last 2 seconds with a second keyframe.
- Announce differently by editing the two live-region strings.
- Persist across reloads by mirroring state + deadline into localStorage.
Gotchas — read before shipping
- @property is required for smooth conic animation — without registration the gradient snaps; ship the static ring as fallback (done here).
- Keep the timeout the single source of truth and derive visuals from it — never let CSS animation end-state imply the real state.
- aria-live regions must exist in the DOM before you write to them, or the first announcement is swallowed.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Floor set by oklch(), @property as this demo is written. The core technique itself goes back further — Chrome 85+.
@property powers the smooth ring; on older engines the toggle and timer still work with a stepped ring. Core switch is universal.