Buttons✨ Premium

Gummy Elastic

On hover the button grows 10% and rounds off; on press it squashes — an overshooting cubic-bezier curve does all the bounce, no JS.

CSSHoverElastic

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Buttons has 27 effects, including 2 free. Explore the category →

3 usage examples

Check an answer — quiz or learning app — Gummy Elastic example 1

① Check an answer — quiz or learning app

WhenQuestion screen with choices, then a confirmation button.
WhyA playful universe welcomes exaggeration: the button that swells on hover and squashes on press gives physical feedback without sound or imagery, and the curve is felt on release, right when the answer lands.
SettingsHover scale(1.08), press scale(.92), duration .35s for a softer bounce.
“Like” button on a post card — Gummy Elastic example 2

② “Like” button on a post card

WhenAction bar under a post: like, comment, share.
WhyThe bounce on click has been the convention for reactions since mobile apps; in pure CSS it costs nothing and also works on a link or a label.
SettingsReduced padding (8px 14px), border-radius: 999px on hover for a pill, scale(1.15) on the icon only through a child span.
“Let's go” — last step of an onboarding — Gummy Elastic example 3

③ “Let's go” — last step of an onboarding

WhenEnd-of-setup screen: a success message, one large button.
WhyAn isolated, generous button lets the curve express itself: the 9% overshoot is visible at that size and closes the flow on an upbeat note.
SettingsCurve cubic-bezier(.68, -.6, .32, 1.6) (stronger overshoot), duration .4s, border-radius: 40px on hover, 18 px text.

How it works

Three states, all in CSS: rest (scale(1), border-radius: 12px inherited from .demo-btn), :hover (transform: scale(1.1); border-radius: 30px) and :active (transform: scale(.95); border-radius: 8px). No @keyframes: the motion is nothing but the transition between these states.

The transition: transition: .3s cubic-bezier(.68, -.55, .265, 1.55) on .btn-gummy — property all (the default), 300 ms. The control points outside [0, 1] on Y (−.55 and 1.55) push the curve outside the start → end range: it first pulls back about 9% of the distance (around 60 ms), then overshoots the target by about 9% (around 235 ms) before settling. For scale 1 → 1.1: dip near 0.99, peak near 1.11.

border-radius rides the same curve: 12 → 30 px overshoots to ≈ 31.6 px and comes back; on press, 30 → 8 px. The simultaneous change of shape and size is what reads as rubber — a bounce on scale alone would look like a zoom.

Rule order matters: :active is declared after :hover, so while the mouse button is down the squashed state wins. On release, the button returns to the hover state through the same 300 ms curve — the release bounce costs nothing extra.

Accessibility

  • prefers-reduced-motion not handled: add @media (prefers-reduced-motion: reduce) { .btn-gummy { transition: none } }. Hover and press states still change, but instantly and without overshoot.
  • Keyboard: :active applies while Space is held in most browsers, but there is no :hover on the keyboard. Declare .btn-gummy:focus-visible { transform: scale(1.1); border-radius: 30px } to give Tab navigation the same relief as hover.
  • Contrast: white text on #ec4899 = 3.5:1, below the 4.5:1 AA threshold for 16 px text. #db2777 gives 4.6:1 and #be185d 6.0:1. Scaling changes nothing about contrast.
  • The code sets border: none without touching outline: the native focus ring stays and follows the transform (outline is not clipped by overflow: hidden).
  • Touch: on iOS the :hover state sticks after a tap (the button stays at 1.1 until the next touch); wrap the hover rule in @media (hover: hover) to avoid it. Target ≈ 52 px tall, 57 px on hover — above the 44 px minimum.

Browser compatibility

Requires CSS transitions on transform and border-radius with cubic-bezier() — every browser since 2012, overshoot values included. No JavaScript, zero dependencies.

Chrome 26+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 12+✓ Full
Mobile iOS✓ Full (:hover sticks after a tap)
Android Chrome✓ Full

Without transitions, the three states apply instantly: the button stays usable, only the bounce is gone.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
cubic-bezier(.68, -.55, .265, 1.55) (CSS — transition) ≈ 9% pull-back then 9% overshoot Bring the 2nd and 4th parameters toward 0 and 1 to calm the bounce (.68, -.2, .265, 1.2); push them further to exaggerate it (-.8 / 1.8). Too far, border-radius dips below 0 for an instant: square corners.
transition-duration (CSS — .btn-gummy) .3s Total length of a transition, bounce included. .2s = snappy; .5s = gelatinous.
scale(1.1) (CSS — :hover) 1.1 Hover growth. Leave room: at 1.1, a 200 px button spills 10 px on each side.
scale(.95) (CSS — :active) .95 Squash on press. Combine with translateY(2px) for a push-in.
border-radius (CSS — 12px rest / 30px hover / 8px press) 12 / 30 / 8 px The shape change is what makes it “gummy”. Set hover to 999px for a button that morphs into a pill.
transition-property (CSS — implicit) all Restrict to transform, border-radius so a color or box-shadow change isn't animated with the same bouncy curve.
background (CSS — .btn-gummy) #ec4899 Solid color. For AA contrast with white text: #db2777 or darker.

FAQ

That's the curve's 2nd parameter, -.55: the value drops below the starting point for roughly the first 60 milliseconds (≈ −9% of the distance, i.e. scale(.99)). It's the anticipation that makes the bounce believable; set that parameter to 0 to remove it.
transform: scale() enlarges the text's rasterized rendering during the transition; on arrival the browser re-rasterizes it sharp. Over 300 ms it's rarely noticeable. If it is, keep scale under 1.05 and let border-radius, which doesn't touch the text, carry most of the effect.
The rules don't depend on the tag. Put .btn-gummy on an <a> set to display: inline-block (transform has no effect on a pure inline element) and keep :hover / :active. For a lone icon, fix border-radius: 50% in all three states: only the scale bounces.