20 Pure CSS Toggles & Switches
Bouncy Pebble
A chunky outlined toggle with an organic "pebble" thumb shape built from 8-value border-radius syntax. Bouncy springy transition with anticipation + overshoot. Track and thumb swap colors on each state. The reference for hand-crafted, designer-drawn toggles — distinctive from every other demo in the collection.
Bouncy Pebble the 19th of 20 designs in the 20 Pure CSS Toggles & Switches collection. The design is implemented in pure CSS — no JavaScript required. Copy the HTML and CSS panels below into your project. Because the demo is pure CSS, it works in any framework or templating engine you happen to use. The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<label class="tg-bp">
<input class="tg-bp-input" type="checkbox" checked>
<span class="tg-bp-track" aria-hidden="true">
<span class="tg-bp-thumb"></span>
</span>
<span class="tg-bp-label">Theme: butter</span>
</label> .tg-bp {
display: inline-flex;
align-items: center;
gap: 16px;
cursor: pointer;
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
font-size: 14px;
color: #f0eeff;
user-select: none;
}
.tg-bp-input {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0);
white-space: nowrap; border: 0;
}
.tg-bp-track {
position: relative;
width: 150px;
height: 74px;
background: #f3f3f3;
border-radius: 999px;
box-shadow:
inset 0 0 0 3px #2a2a2a,
0 10px 20px rgba(0,0,0,0.35);
transition: background 0.35s ease;
}
.tg-bp-thumb {
position: absolute;
top: 50%;
left: 10px;
width: 48px;
height: 48px;
background: #f4df72;
border: 3px solid #2b2b2b;
box-shadow: 0 5px 10px rgba(0,0,0,0.25);
/* 8-value border-radius (horizontal × vertical radii per corner)
creates the organic asymmetric pebble shape: fuller left, tighter
right, subtle diagonal tension. The -2deg tilt adds hand-crafted
irregularity. The cubic-bezier easing has anticipation (negative
control point) and overshoot (>1 control point) — the thumb
pulls back slightly before sliding, then overshoots the
destination before settling. */
border-radius: 52% 38% 42% 58% / 52% 42% 58% 48%;
transform: translateY(-50%) rotate(-2deg);
transition: all 0.35s cubic-bezier(0.68, -0.4, 0.27, 1.4);
}
.tg-bp-input:checked ~ .tg-bp-track {
background: #f4df72;
}
.tg-bp-input:checked ~ .tg-bp-track .tg-bp-thumb {
/* Slides to right edge. Track is 150px wide, thumb is 48px + 3px×2
border = 54px total. left: calc(100% - 64px) gives 10px from the
right edge, mirroring the 10px from the left in the off state. */
left: calc(100% - 64px);
background: #f5f5f5;
/* Mirrored border-radius — horizontal asymmetry flips. The "fuller"
side is now the right (the direction the thumb just came from). */
border-radius: 38% 52% 58% 42% / 42% 52% 48% 58%;
transform: translateY(-50%) rotate(2deg);
}
.tg-bp-input:focus-visible ~ .tg-bp-track {
outline: 3px solid #7c6cff;
outline-offset: 4px;
}
@media (prefers-reduced-motion: reduce) {
.tg-bp-track,
.tg-bp-thumb { transition: none; }
}