47 CSS Toggles & Switches05 / 47

Pure CSSMIT licensed

Bouncy Elastic Physics Toggle

Premium landing-page motion in one line of CSS: an overshooting cubic-bezier makes the thumb wind up backwards, shoot across the track, and bounce gently off the far wall — playful, physics-driven, and still just a checkbox with a transition.

Published

Live Demo
Try it

The code

<section class="tgl-05" aria-label="Bouncy elastic physics toggle">
  <label class="tgl-05__row" for="tgl-05-sw">
    <span class="tgl-05__lbl">Confetti on publish</span>
    <input type="checkbox" id="tgl-05-sw" class="tgl-05__sw" role="switch">
  </label>
  <p class="tgl-05__cap">cubic-bezier(0.4, −0.3, 0.6, 1.3)</p>
</section>
.tgl-05,
.tgl-05 *,
.tgl-05 *::before,
.tgl-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tgl-05 {
  --on: oklch(0.7 0.17 150);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 60px 20px;
  background: #fdf8ef;
}

.tgl-05__row {
  display: flex;
  align-items: center;
  gap: 34px;
  cursor: pointer;
  background: #fff;
  border: 1px solid #eee3cf;
  border-radius: 18px;
  padding: 20px 26px;
  box-shadow: 0 10px 26px -18px rgba(120,90,20,.35);
}

.tgl-05__lbl {
  font-size: 16px;
  font-weight: 600;
  color: #4a4132;
}

.tgl-05__sw {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 76px;
  height: 40px;
  border-radius: 22px;
  background: #e8e0d0;
  position: relative;
  cursor: pointer;
  transition: background-color .45s ease;
}

.tgl-05__sw:checked {
  background: var(--on);
}

.tgl-05__sw::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 3px 7px rgba(80,60,20,.3);
  transition: transform .55s cubic-bezier(0.4,-0.3,0.6,1.3);
}

.tgl-05__sw:checked::before {
  transform: translateX(36px);
}

.tgl-05__sw:active::before {
  transform: scale(.92);
}

.tgl-05__sw:checked:active::before {
  transform: translateX(36px) scale(.92);
}

.tgl-05__sw:focus-visible {
  outline: 3px solid var(--on);
  outline-offset: 4px;
}

.tgl-05__cap {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 12px;
  color: #b0a58d;
}

@media (prefers-reduced-motion: reduce) {
  .tgl-05__sw::before {
    transition: transform .2s ease;
  }
}
/* No JavaScript — a single overshooting cubic-bezier on the thumb's transform creates the wind-up, launch and wall-bounce. */
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 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: Bouncy Elastic Physics Toggle
Source: https://codefronts.com/components/css-toggles-switches/bouncy-elastic-physics-toggle/

Premium landing-page motion in one line of CSS: an overshooting cubic-bezier makes the thumb wind up backwards, shoot across the track, and bounce gently off the far wall — playful, physics-driven, and still just a checkbox with a transition.
## HTML
```html
<section class="tgl-05" aria-label="Bouncy elastic physics toggle">
  <label class="tgl-05__row" for="tgl-05-sw">
    <span class="tgl-05__lbl">Confetti on publish</span>
    <input type="checkbox" id="tgl-05-sw" class="tgl-05__sw" role="switch">
  </label>
  <p class="tgl-05__cap">cubic-bezier(0.4, −0.3, 0.6, 1.3)</p>
</section>
```
## CSS
```css
.tgl-05,
.tgl-05 *,
.tgl-05 *::before,
.tgl-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tgl-05 {
  --on: oklch(0.7 0.17 150);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 60px 20px;
  background: #fdf8ef;
}

.tgl-05__row {
  display: flex;
  align-items: center;
  gap: 34px;
  cursor: pointer;
  background: #fff;
  border: 1px solid #eee3cf;
  border-radius: 18px;
  padding: 20px 26px;
  box-shadow: 0 10px 26px -18px rgba(120,90,20,.35);
}

.tgl-05__lbl {
  font-size: 16px;
  font-weight: 600;
  color: #4a4132;
}

.tgl-05__sw {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 76px;
  height: 40px;
  border-radius: 22px;
  background: #e8e0d0;
  position: relative;
  cursor: pointer;
  transition: background-color .45s ease;
}

.tgl-05__sw:checked {
  background: var(--on);
}

.tgl-05__sw::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 3px 7px rgba(80,60,20,.3);
  transition: transform .55s cubic-bezier(0.4,-0.3,0.6,1.3);
}

.tgl-05__sw:checked::before {
  transform: translateX(36px);
}

.tgl-05__sw:active::before {
  transform: scale(.92);
}

.tgl-05__sw:checked:active::before {
  transform: translateX(36px) scale(.92);
}

.tgl-05__sw:focus-visible {
  outline: 3px solid var(--on);
  outline-offset: 4px;
}

.tgl-05__cap {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 12px;
  color: #b0a58d;
}

@media (prefers-reduced-motion: reduce) {
  .tgl-05__sw::before {
    transition: transform .2s ease;
  }
}
```

## JavaScript
```js
/* No JavaScript — a single overshooting cubic-bezier on the thumb's transform creates the wind-up, launch and wall-bounce. */
```

How this works

The entire effect is the timing function cubic-bezier(0.4, -0.3, 0.6, 1.3) applied to the thumb's transform. The negative first y-value pulls progress below 0 — the anticipation wind-up — and the 1.3 end overshoots the target before settling, reading as an elastic bounce against the track wall.

Because the tween is a single compositor-driven transform transition, it costs nothing on the main thread: no keyframes, no spring library, perfect INP. The track color fades on a plain ease so the color change doesn't wobble with the thumb.

Make it yours

  • More drama: push the bezier to (0.4, -0.6, 0.5, 1.6) — anticipation and bounce both grow.
  • Stiffer spring: shorten the duration to .35s.
  • Apply the same bezier to the :active squash for a squishier press.
  • Tune travel distance by resizing the track (thumb travel = width − thumb − 2×inset).

Gotchas — read before shipping

  • Overshoot beziers need travel headroom — the thumb overshoots ~15% past its target, so keep the track inset ≥ 4px or it visually clips.
  • Don't reuse the bouncy bezier on the background-color fade; overshooting a color looks like flicker.
  • One transition, one property: chaining bounces with keyframes here would fight the reverse (uncheck) direction.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 16+.

Custom cubic-bezier() timing with y-values outside [0,1] is supported everywhere; this is among the most portable demos in the collection.

Techniques used in this demo

Search CodeFronts

Loading…