Bounce in
Entrance animation that scales elements from zero with an elastic overshoot for a playful, spring-like reveal — no JavaScript.
Quick implementation
.bounce-in {
animation: bounce-in 0.6s ease both;
}
@keyframes bounce-in {
0% { transform: scale(0); opacity: 0; }
60% { transform: scale(1.15); opacity: 1; }
80% { transform: scale(0.95); }
100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.bounce-in {
animation: none;
opacity: 1;
transform: none;
}
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building entrance animations for a UI.
Goal: A bounce-in entrance animation that scales elements from zero to their
natural size with an elastic overshoot — giving a spring-like, playful feel
with no JavaScript.
Technical constraints:
- Use a multi-step @keyframes:
0% { transform: scale(0); opacity: 0; }
60% { transform: scale(1.15); opacity: 1; }
80% { transform: scale(0.95); }
100% { transform: scale(1); opacity: 1; }
- Apply animation: bounce-in 0.6s ease both; — the "both" fill mode keeps
the element invisible and scaled to zero before the animation starts and
holds the final state after it ends.
- Use oklch() for any colors — no hex or rgba values.
- Use CSS custom properties (var(--card), var(--text), etc.) for theming.
- Include @media (prefers-reduced-motion: reduce) that sets animation: none,
opacity: 1, and transform: none on the animated element.
Framework variant (pick one):
A) Vanilla CSS class .bounce-in applicable to any element, with
:nth-child() selectors for staggered animation-delay values.
B) React component — accept children, delay (number in seconds), and
duration props; apply the class and inline animation-delay via style.
Edge cases to handle:
- Elements that start at scale(0) are invisible but still occupy space in
the layout; if they flash or cause layout shift before animating, ensure
animation-fill-mode is set to "both" and not "forwards" alone.
- Overshoot values above ~1.2 can feel jarring; keep scale at the 60%
keyframe between 1.1 and 1.2 for a natural spring feel.
- Staggered lists should use animation-delay increments (e.g. 0.1s per item)
rather than re-running the animation via JavaScript class toggling.
- The transform-origin defaults to center — if the element should bounce in
from a corner or edge, set transform-origin explicitly.
Return CSS only (or a React component if variant B is chosen).
Why bounce feels natural
The bounce-in pattern draws directly from the physics of elastic materials. When a real spring-loaded object snaps into place, it doesn't stop dead at the target — it overshoots, compresses back slightly, then settles. The keyframe sequence mimics this: the element grows past its intended size at 60%, pulls back a little at 80%, then lands at exactly scale(1). That two-step overshoot-and-recover is what makes the animation feel alive rather than mechanical.
Standard CSS easing functions like ease-in-out approximate this for position, but for scale-based entrance animations the multi-step @keyframes approach gives you direct control over each inflection point. You decide exactly how far the element overshoots and how quickly it recovers — without needing cubic-bezier() values or a JavaScript spring library.
The multi-step keyframe logic
The four keyframe stops each serve a distinct purpose. At 0%, scale(0) and opacity: 0 make the element completely invisible — it takes up no visual space. By 60% the element has already become fully opaque and overshot to scale(1.15), 15% larger than its resting size. This overshoot happens in the first 60% of the duration, so most of the motion is front-loaded and fast.
At 80%, the scale pulls back to scale(0.95) — a slight undershoot that creates the elastic snap. The final 100% keyframe lands cleanly at scale(1). The ease timing function applies within each segment, so the overall curve is smooth rather than linear.
The both fill mode on animation is load-bearing: it applies the 0% values before the animation fires (keeping the element invisible until it's time to bounce in) and locks the 100% values in place after the animation finishes (so the element doesn't disappear when the keyframe ends).
Performance & accessibility
Like all animations that rely exclusively on transform and opacity, bounce-in runs entirely on the compositor thread. The browser hands it off to the GPU and animates it independently of the main thread. No layout recalculations, no repaints — the animation stays smooth even when JavaScript is busy or the page is content-heavy.
Staggering via animation-delay on :nth-child() selectors is the right approach for lists of bouncing elements. Incrementing by about 0.1–0.15 s per item gives a pleasing cascade without making users wait too long for the last item. Avoid toggling classes from JavaScript just to replay the animation; instead, briefly set animation: none, force a reflow with offsetHeight, then restore the class — as the replay button on this page demonstrates.
The prefers-reduced-motion: reduce block is non-negotiable. The rapid scale change of a bounce can be disorienting for users with vestibular disorders. Setting animation: none, opacity: 1, and transform: none inside the query ensures those users see every element in its final visible state instantly — no motion, no flash of invisible content caused by the both fill mode.