20 CSS Animated Buttons 17 / 20
CSS Animated Toggle Switch Button
A collection of iOS-style animated toggle switches for dark mode, notifications, and auto-save — styled with pure CSS and driven by accessible checkbox inputs.
The code
<div class="ab-17">
<p class="ab-17__heading">Preferences</p>
<div class="ab-17__list">
<div class="ab-17__row" id="ab-17-row-0">
<div class="ab-17__info">
<p class="ab-17__title">Dark Mode</p>
<p class="ab-17__status" id="ab-17-s0">Off</p>
</div>
<div class="ab-17__toggle" style="--on-color:#7c3aed">
<input class="ab-17__input" type="checkbox" id="ab-17-t0" />
<label class="ab-17__track" for="ab-17-t0"><span class="ab-17__knob"></span></label>
</div>
</div>
<div class="ab-17__row" id="ab-17-row-1">
<div class="ab-17__info">
<p class="ab-17__title">Push Notifications</p>
<p class="ab-17__status" id="ab-17-s1">Off</p>
</div>
<div class="ab-17__toggle" style="--on-color:#0891b2">
<input class="ab-17__input" type="checkbox" id="ab-17-t1" />
<label class="ab-17__track" for="ab-17-t1"><span class="ab-17__knob"></span></label>
</div>
</div>
<div class="ab-17__row" id="ab-17-row-2">
<div class="ab-17__info">
<p class="ab-17__title">Auto-Save</p>
<p class="ab-17__status" id="ab-17-s2">Off</p>
</div>
<div class="ab-17__toggle" style="--on-color:#059669">
<input class="ab-17__input" type="checkbox" id="ab-17-t2" checked />
<label class="ab-17__track" for="ab-17-t2"><span class="ab-17__knob"></span></label>
</div>
</div>
</div>
</div> <div class="ab-17">
<p class="ab-17__heading">Preferences</p>
<div class="ab-17__list">
<div class="ab-17__row" id="ab-17-row-0">
<div class="ab-17__info">
<p class="ab-17__title">Dark Mode</p>
<p class="ab-17__status" id="ab-17-s0">Off</p>
</div>
<div class="ab-17__toggle" style="--on-color:#7c3aed">
<input class="ab-17__input" type="checkbox" id="ab-17-t0" />
<label class="ab-17__track" for="ab-17-t0"><span class="ab-17__knob"></span></label>
</div>
</div>
<div class="ab-17__row" id="ab-17-row-1">
<div class="ab-17__info">
<p class="ab-17__title">Push Notifications</p>
<p class="ab-17__status" id="ab-17-s1">Off</p>
</div>
<div class="ab-17__toggle" style="--on-color:#0891b2">
<input class="ab-17__input" type="checkbox" id="ab-17-t1" />
<label class="ab-17__track" for="ab-17-t1"><span class="ab-17__knob"></span></label>
</div>
</div>
<div class="ab-17__row" id="ab-17-row-2">
<div class="ab-17__info">
<p class="ab-17__title">Auto-Save</p>
<p class="ab-17__status" id="ab-17-s2">Off</p>
</div>
<div class="ab-17__toggle" style="--on-color:#059669">
<input class="ab-17__input" type="checkbox" id="ab-17-t2" checked />
<label class="ab-17__track" for="ab-17-t2"><span class="ab-17__knob"></span></label>
</div>
</div>
</div>
</div>.ab-17,.ab-17 *,.ab-17 *::before,.ab-17 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-17 ::selection{background:#7c3aed;color:#fff}
.ab-17{
font-family:system-ui,sans-serif;
background:#f8fafc;
min-height:100vh;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
padding:2rem;
}
.ab-17__heading{
font-size:.8rem;
font-weight:700;
letter-spacing:.14em;
text-transform:uppercase;
color:#64748b;
margin-bottom:1.25rem;
align-self:flex-start;
max-width:380px;
width:100%;
}
.ab-17__list{width:100%;max-width:380px;display:flex;flex-direction:column;gap:.5rem}
.ab-17__row{
display:flex;
align-items:center;
justify-content:space-between;
padding:1rem;
background:#fff;
border-radius:12px;
border:1.5px solid #e2e8f0;
transition:border-color .3s,background .3s;
}
.ab-17__row.is-active{border-color:rgba(124,58,237,.3);background:#faf7ff}
.ab-17__title{font-size:.93rem;font-weight:600;color:#1e293b}
.ab-17__status{font-size:.78rem;color:#94a3b8;margin-top:.15rem}
.ab-17__toggle{position:relative}
.ab-17__input{
position:absolute;
opacity:0;
width:0;height:0;
}
.ab-17__track{
--on-color:#7c3aed;
display:flex;
align-items:center;
width:50px;height:28px;
background:#cbd5e1;
border-radius:50px;
cursor:pointer;
transition:background .3s;
padding:3px;
}
.ab-17__input:checked+.ab-17__track{background:var(--on-color)}
.ab-17__knob{
width:22px;height:22px;
background:#fff;
border-radius:50%;
box-shadow:0 1px 4px rgba(0,0,0,.2);
transition:transform .3s cubic-bezier(.4,0,.2,1);
}
.ab-17__input:checked+.ab-17__track .ab-17__knob{transform:translateX(22px)}
.ab-17__input:focus-visible+.ab-17__track{outline:2px solid #7c3aed;outline-offset:2px}
@media(prefers-reduced-motion:reduce){
.ab-17__knob,.ab-17__track,.ab-17__row{transition:none}
} .ab-17,.ab-17 *,.ab-17 *::before,.ab-17 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-17 ::selection{background:#7c3aed;color:#fff}
.ab-17{
font-family:system-ui,sans-serif;
background:#f8fafc;
min-height:100vh;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
padding:2rem;
}
.ab-17__heading{
font-size:.8rem;
font-weight:700;
letter-spacing:.14em;
text-transform:uppercase;
color:#64748b;
margin-bottom:1.25rem;
align-self:flex-start;
max-width:380px;
width:100%;
}
.ab-17__list{width:100%;max-width:380px;display:flex;flex-direction:column;gap:.5rem}
.ab-17__row{
display:flex;
align-items:center;
justify-content:space-between;
padding:1rem;
background:#fff;
border-radius:12px;
border:1.5px solid #e2e8f0;
transition:border-color .3s,background .3s;
}
.ab-17__row.is-active{border-color:rgba(124,58,237,.3);background:#faf7ff}
.ab-17__title{font-size:.93rem;font-weight:600;color:#1e293b}
.ab-17__status{font-size:.78rem;color:#94a3b8;margin-top:.15rem}
.ab-17__toggle{position:relative}
.ab-17__input{
position:absolute;
opacity:0;
width:0;height:0;
}
.ab-17__track{
--on-color:#7c3aed;
display:flex;
align-items:center;
width:50px;height:28px;
background:#cbd5e1;
border-radius:50px;
cursor:pointer;
transition:background .3s;
padding:3px;
}
.ab-17__input:checked+.ab-17__track{background:var(--on-color)}
.ab-17__knob{
width:22px;height:22px;
background:#fff;
border-radius:50%;
box-shadow:0 1px 4px rgba(0,0,0,.2);
transition:transform .3s cubic-bezier(.4,0,.2,1);
}
.ab-17__input:checked+.ab-17__track .ab-17__knob{transform:translateX(22px)}
.ab-17__input:focus-visible+.ab-17__track{outline:2px solid #7c3aed;outline-offset:2px}
@media(prefers-reduced-motion:reduce){
.ab-17__knob,.ab-17__track,.ab-17__row{transition:none}
}(function(){
var ids=[0,1,2];
ids.forEach(function(i){
var inp=document.getElementById('ab-17-t'+i);
var status=document.getElementById('ab-17-s'+i);
var row=document.getElementById('ab-17-row-'+i);
if(!inp||!status||!row)return;
function update(){
var on=inp.checked;
status.textContent=on?'On':'Off';
row.classList.toggle('is-active',on);
}
update();
inp.addEventListener('change',update);
});
})(); (function(){
var ids=[0,1,2];
ids.forEach(function(i){
var inp=document.getElementById('ab-17-t'+i);
var status=document.getElementById('ab-17-s'+i);
var row=document.getElementById('ab-17-row-'+i);
if(!inp||!status||!row)return;
function update(){
var on=inp.checked;
status.textContent=on?'On':'Off';
row.classList.toggle('is-active',on);
}
update();
inp.addEventListener('change',update);
});
})();How this works
Each toggle is a hidden <input type="checkbox"> paired with a <label> that acts as the visible toggle track. The track's ::before pseudo-element is the sliding knob. When the checkbox is :checked, a CSS sibling combinator (input:checked + .ab-17__track) transitions the track background from grey to the accent colour and translates the knob from left to right via ::before { transform: translateX(24px) }. No JavaScript is needed for the toggle itself.
JS is used only to read the toggle state and update adjacent status labels — changing text from "Off" to "On" and applying the .is-active class to the row for a subtle highlight. An aria-checked attribute is maintained in sync by the JS layer for screen-reader support beyond what the native checkbox provides. The result is a fully accessible toggle with graceful degradation to a native checkbox if JS is unavailable.
Customize
- Change the on-colour by updating
--on-coloron each.ab-17__toggleor globally on.ab-17to apply uniformly. - Increase the knob travel distance by raising the
translateXininput:checked + .ab-17__track::beforefrom24pxto28pxand matching the trackwidth. - Add a sun/moon icon inside the knob for a dark-mode toggle by setting a Unicode character in
::before { content: "☀️" }and swapping to"🌙"when checked. - Animate the label text simultaneously by using a CSS
transition: color .3son the label and targeting it through the sibling combinator. - Create a three-state toggle (Off / Partial / Full) by using a
rangeinput with fixed stops and mapping knob position to three CSS custom property values.
Watch out for
- The
input:checked + label::beforeCSS combinator only works when the label immediately follows the input in the DOM — wrapping either in an extra div will break the selector. - Setting
display: noneon the checkbox hides it from assistive technologies — useposition: absolute; opacity: 0; width: 0; height: 0instead to keep it accessible. - On iOS Safari, custom checkbox styling requires
-webkit-appearance: noneon the input — omitting this may render the native checkbox overlaying the custom track.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 26+ | 9+ | 16+ | 26+ |
CSS sibling combinator and pseudo-element transforms are universally supported; -webkit-appearance: none handles Safari.