20 CSS Animated Buttons 09 / 20
CSS Jelly Bounce Button Animation
Playful elastic micro-interaction buttons that wobble with a jelly-like spring on hover and snap back with an overshoot settle using CSS scale and skew keyframes.
The code
<div class="ab-09">
<p class="ab-09__label">Hover for jelly effect</p>
<div class="ab-09__row">
<button class="ab-09__btn ab-09__btn--pink">Subscribe!</button>
<button class="ab-09__btn ab-09__btn--lime">Let's Go!</button>
<button class="ab-09__btn ab-09__btn--sky">Join Now!</button>
</div>
</div> <div class="ab-09">
<p class="ab-09__label">Hover for jelly effect</p>
<div class="ab-09__row">
<button class="ab-09__btn ab-09__btn--pink">Subscribe!</button>
<button class="ab-09__btn ab-09__btn--lime">Let's Go!</button>
<button class="ab-09__btn ab-09__btn--sky">Join Now!</button>
</div>
</div>.ab-09,.ab-09 *,.ab-09 *::before,.ab-09 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-09 ::selection{background:#ec4899;color:#fff}
.ab-09{
font-family:system-ui,sans-serif;
background:#fdf2ff;
min-height:100vh;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
gap:2rem;
padding:2rem;
}
.ab-09__label{
font-size:.78rem;
letter-spacing:.14em;
text-transform:uppercase;
color:#a855f7;
opacity:.6;
}
.ab-09__row{
display:flex;
gap:1.25rem;
flex-wrap:wrap;
justify-content:center;
}
.ab-09__btn{
--c:#ec4899;
padding:.85rem 2.2rem;
font-size:1.05rem;
font-weight:800;
color:#fff;
background:var(--c);
border:none;
border-radius:50px;
cursor:pointer;
outline:none;
letter-spacing:.03em;
transform-origin:center;
box-shadow:0 4px 16px rgba(0,0,0,.15);
will-change:transform;
}
.ab-09__btn--pink{--c:#ec4899}
.ab-09__btn--lime{--c:#16a34a}
.ab-09__btn--sky{--c:#0284c7}
.ab-09__btn:hover{animation:ab-09-jelly .7s cubic-bezier(.4,0,.2,1) both}
@keyframes ab-09-jelly{
0%{transform:scale(1)}
10%{transform:scaleX(1.2) scaleY(.85)}
25%{transform:scaleX(.85) scaleY(1.15)}
40%{transform:scaleX(1.1) scaleY(.92)}
55%{transform:scaleX(.95) scaleY(1.05)}
70%{transform:scaleX(1.03) scaleY(.98)}
85%{transform:scaleX(.99) scaleY(1.01)}
100%{transform:scale(1)}
}
@media(prefers-reduced-motion:reduce){
.ab-09__btn:hover{animation:none;transform:scale(1.04)}
} .ab-09,.ab-09 *,.ab-09 *::before,.ab-09 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-09 ::selection{background:#ec4899;color:#fff}
.ab-09{
font-family:system-ui,sans-serif;
background:#fdf2ff;
min-height:100vh;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
gap:2rem;
padding:2rem;
}
.ab-09__label{
font-size:.78rem;
letter-spacing:.14em;
text-transform:uppercase;
color:#a855f7;
opacity:.6;
}
.ab-09__row{
display:flex;
gap:1.25rem;
flex-wrap:wrap;
justify-content:center;
}
.ab-09__btn{
--c:#ec4899;
padding:.85rem 2.2rem;
font-size:1.05rem;
font-weight:800;
color:#fff;
background:var(--c);
border:none;
border-radius:50px;
cursor:pointer;
outline:none;
letter-spacing:.03em;
transform-origin:center;
box-shadow:0 4px 16px rgba(0,0,0,.15);
will-change:transform;
}
.ab-09__btn--pink{--c:#ec4899}
.ab-09__btn--lime{--c:#16a34a}
.ab-09__btn--sky{--c:#0284c7}
.ab-09__btn:hover{animation:ab-09-jelly .7s cubic-bezier(.4,0,.2,1) both}
@keyframes ab-09-jelly{
0%{transform:scale(1)}
10%{transform:scaleX(1.2) scaleY(.85)}
25%{transform:scaleX(.85) scaleY(1.15)}
40%{transform:scaleX(1.1) scaleY(.92)}
55%{transform:scaleX(.95) scaleY(1.05)}
70%{transform:scaleX(1.03) scaleY(.98)}
85%{transform:scaleX(.99) scaleY(1.01)}
100%{transform:scale(1)}
}
@media(prefers-reduced-motion:reduce){
.ab-09__btn:hover{animation:none;transform:scale(1.04)}
}How this works
The jelly effect chains scaleX and scaleY transforms through a multi-step keyframe (ab-09-jelly): the button first squashes horizontally (scaleX(1.2) scaleY(.9)), then stretches vertically (scaleX(.85) scaleY(1.1)), and finally oscillates back through diminishing overshoot values before settling at scale(1). This mimics the physics of a jelly object being compressed and released. All transforms operate from the button's natural centre (transform-origin: center), so the effect stays spatially stable.
The animation is triggered by the :hover state, and because it ends at the identity transform, the button naturally returns to normal when the cursor leaves mid-animation. animation-fill-mode: both ensures the first frame is applied immediately on hover without a single frame at the resting state. Combining with a slight filter: brightness pulse makes the jelly feel more alive, as if it's reacting to light during deformation.
Customize
- Intensify the jelly effect by increasing the extreme scale values (e.g.
scaleX(1.35)for squash andscaleY(1.2)for stretch). - Add a colour flash during the peak squash by transitioning
filter: hue-rotate(30deg)at the 20% keyframe step. - Create an idle bounce by adding a secondary
animation: ab-09-idle 2s ease-in-out infinitewith gentlescaleY(1.02)pulses between hover interactions. - Delay the jelly on staggered rows by giving each button an increasing
animation-delay— e.g.0s, .1s, .2s— for a wave effect across a button group. - Reduce the settle wobble for a tighter feel by cutting the oscillation keyframe count from 7 steps to 5, eliminating the final two minor overshoot steps.
Watch out for
- The
scaleX/scaleYcombination can cause text to appear slightly blurry during the squash step on low-DPI screens — this is a sub-pixel rendering artefact and is usually imperceptible at normal viewing distances. - If the button is inside a card with
overflow: hidden, the scale-up phase may be clipped — ensure the container has enough padding or use a wrapper withoverflow: visible. - On Safari, combined scale transforms occasionally trigger a repaint of sibling elements — isolate the button in its own stacking context with
isolation: isolateif you see neighbouring content flashing.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 36+ | 9+ | 16+ | 36+ |
CSS transform animations are universally supported; the multi-step keyframe works in all modern engines.