Jelly button: squash & stretch on press
Make a control feel physical: it squashes when pressed and springs back with a wobble.
In one line: a button that briefly squashes when you press it and springs back with a little overshoot reads as physical, responsive, and alive — at the cost of about twenty lines of code.
What it is
"Squash and stretch" is a borrowed animation principle: an object compresses along one axis and expands along the other when force hits it, then settles. On a button, the "force" is your press. The control scales down and slightly wider on press, then springs back past its resting size before settling — a tiny wobble that signals I felt that.
Why it matters
A flat state change ("pressed → not pressed") tells the eye nothing about cause. A spring tells a cause-and-effect story: you pushed, it gave, it recovered. That micro-story is what makes an interface feel responsive rather than merely functional, and it costs almost nothing. The trick is restraint — the wobble should be felt more than seen. Overdo the overshoot and the control feels gelatinous and slow; underdo it and you've just added latency.
See it
Hold to squash, release to feel the spring settle.
Tweak it3
How it works
Three moves do all the work:
- On press, scale the element down and wide:
scaleXup a touch,scaleYdown. That's the squash. - On release, animate back to
scale: 1with a spring, not a fixed-duration ease. A spring naturally overshoots and oscillates, which is the wobble — you don't keyframe it by hand. - Tune the spring so it settles in roughly 300–450 ms. Higher stiffness = snappier; lower damping = more wobble.
The settle time and overshoot you see above are the same numbers used in the code below. Reach for a spring (not ease-out) whenever the motion should feel like a physical recovery rather than a scripted move.
Build it
.jelly-btn {
/* resting state */
transform: scale(1);
transition: transform 120ms ease;
transform-origin: center;
}
/* held down: squash (wider + shorter) */
.jelly-btn:active {
transform: scale(1.06, 0.92);
transition-duration: 80ms;
}
/* released: play the springy recovery */
.jelly-btn.is-released {
animation: jelly-settle 420ms cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes jelly-settle {
0% { transform: scale(1.06, 0.92); }
40% { transform: scale(0.98, 1.03); } /* overshoot the other way */
70% { transform: scale(1.01, 0.99); }
100% { transform: scale(1, 1); }
}
/* respect users who don't want motion */
@media (prefers-reduced-motion: reduce) {
.jelly-btn,
.jelly-btn:active,
.jelly-btn.is-released {
transition: none;
animation: none;
transform: none;
}
}import { motion, useReducedMotion } from "motion/react";
export function JellyButton({ children, ...props }) {
const reduceMotion = useReducedMotion();
return (
<motion.button
type="button"
className="jelly-btn"
// squash while held; the spring handles the wobble on release
whileTap={reduceMotion ? undefined : { scaleX: 1.06, scaleY: 0.92 }}
transition={{ type: "spring", stiffness: 600, damping: 12, mass: 0.6 }}
{...props}
>
{children}
</motion.button>
);
}Make it yours
Use the controls beside the demo above to change squash amount, spring stiffness, and damping — each change updates the example live.
Experiment in the playgroundReproduce it with an LLM
Reproduce it with an LLM
You are a senior front-end engineer. Build a reusable button component in React using the Motion library that has a 'jelly' squash-and-stretch micro-interaction: on press it scales slightly wider and shorter, and on release it springs back to its resting size with a small overshoot/wobble that settles in about 400 ms. Requirements: it must be a real <button> with keyboard activation and a visible focus-visible ring; it must fully disable the animation when prefers-reduced-motion: reduce is set; animate only transforms (no layout properties); no external CSS framework. Return only the component code with imports — no explanation.
Pitfalls & accessibility
Related
Easing & spring
Easing scripts a move over a fixed time; a spring simulates physics and settles on its own. Knowing which to reach for is half of good motion.
Anatomy of a micro-interaction
Every small feedback moment has four parts — trigger, rules, feedback, and loops. Name them and you can design any of them on purpose.
Tap & gesture feedback
Touch interfaces lack physical clicks, so a tap needs visible feedback — a ripple and a small press — to confirm the surface felt you.
Further reading
- The spring-vs-easing distinction, in your animation library's official motion docs.
- The original twelve principles of animation, for where "squash & stretch" comes from.