20 CSS Animated Buttons17 / 20

CSS + JSMIT licensed

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.

Published

Live Demo
Try it

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>
.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);
  });
})();
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 Animated Button 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: CSS Animated Toggle Switch Button
Source: https://codefronts.com/motion/css-animated-buttons/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.
## HTML
```html
<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>
```
## CSS
```css
.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;
  }
}
```

## JavaScript
```js
(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.

Make it yours

  • Change the on-colour by updating --on-color on each .ab-17__toggle or globally on .ab-17 to apply uniformly.
  • Increase the knob travel distance by raising the translateX in input:checked + .ab-17__track::before from 24px to 28px and matching the track width.
  • 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 .3s on the label and targeting it through the sibling combinator.
  • Create a three-state toggle (Off / Partial / Full) by using a range input with fixed stops and mapping knob position to three CSS custom property values.

Gotchas — read before shipping

  • The input:checked + label::before CSS 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: none on the checkbox hides it from assistive technologies — use position: absolute; opacity: 0; width: 0; height: 0 instead to keep it accessible.
  • On iOS Safari, custom checkbox styling requires -webkit-appearance: none on the input — omitting this may render the native checkbox overlaying the custom track.

Browser support

ChromeSafariFirefoxEdge
26+9+16+26+

CSS sibling combinator and pseudo-element transforms are universally supported; -webkit-appearance: none handles Safari.

Techniques used in this demo

Search CodeFronts

Loading…