Cubic Bezier in Depth: Mastering the Motion Curve
Every time you write transition: transform 0.3s ease-out, you’re using a shorthand that hides a more complex mathematical function. Understanding that function from the ground up lets you produce any motion feel you want instead of picking from five built-in presets.
What a bezier curve is
A cubic bezier is a curve defined by four points: P0 (start), P1 (handle 1), P2 (handle 2), P3 (end). In CSS, P0 is always (0, 0) and P3 is always (1, 1) — only P1 and P2 change.
cubic-bezier(x1, y1, x2, y2)
/* P1 P2 */
The X axis is time (0 = start, 1 = end). The Y axis is the animation’s progress (0 = starting position, 1 = target position). The curve describes the rate at which progress changes over time — not the position.
Reading a curve
ease-out: cubic-bezier(0, 0, 0.58, 1)
- P1 = (0, 0): the first handle sits at the origin → the animation starts fast
- P2 = (0.58, 1): the last handle sits high and to the left → the animation slows down early
ease-in: cubic-bezier(0.42, 0, 1, 1)
- P1 = (0.42, 0): the first handle is shifted strongly to the right → the animation starts slowly
- P2 = (1, 1): the last handle is at the end corner → no deceleration
Why y > 1 creates overshoot
When the y of P1 or P2 exceeds 1.0, the curve produces output values > 1.0 — meaning the animation passes the endpoint before coming back. This is how CSS fakes a spring:
/* Spring — overshoot ~17% */
cubic-bezier(0.34, 1.56, 0.64, 1)
Y = 1.56 creates an overshoot of about 56% beyond the distance from start to end. With transform: translateX(100px), the element travels to ~156px and then returns to 100px.
Similarly, y < 0 creates undershoot — the animation moves in the opposite direction before heading toward the target. Used for a “pull back before launch” effect.
The five CSS presets and how they feel
| Preset | Curve | Feel |
|---|---|---|
linear | (0, 0, 1, 1) | Robotic, unnatural |
ease | (0.25, 0.1, 0.25, 1) | Browser default, a bit lazy |
ease-in | (0.42, 0, 1, 1) | Slow then fast, used for exits |
ease-out | (0, 0, 0.58, 1) | Fast then slow, used for entrances |
ease-in-out | (0.42, 0, 0.58, 1) | Balanced, used for loops |
The problem: all five presets are “generic.” They don’t convey the product’s personality.
Building a custom curve to match a target feel
Snappy / crisp — instant response, settles immediately:
cubic-bezier(0.2, 0, 0, 1)
P1 pulls hard to the right (a fast burst), P2 sits low on the left (abrupt deceleration).
Gentle / soft — unhurried, no rush:
cubic-bezier(0.4, 0, 0.6, 1)
Both handles sit near the center; the curve is gentle and even.
Aggressive / dramatic — slow start, explosive finish:
cubic-bezier(0.8, 0, 0.2, 1)
P1 is far to the right (a large delay before starting), P2 is to the left (strong acceleration at the end).
Material Design standard — a balance between snappy and gentle:
cubic-bezier(0.2, 0, 0, 1) /* standard */
cubic-bezier(0.05, 0.7, 0.1, 1) /* emphasized */
How it differs from a spring
A cubic bezier always has a fixed duration. No matter how complex the curve, the animation ends exactly when transition-duration specifies. A spring has no fixed duration — it settles when velocity approaches 0, and the time depends on stiffness and damping.
Bezier is good for: fades, color, layout shifts, page transitions — anything that needs predictable timing. Spring is good for: interactive response, direct manipulation — anything that needs a physical feel.
See more in Ease vs Spring and Spring Parameters: mass, stiffness, damping.
Tools
Chrome DevTools: click the curve icon next to transition-timing-function and drag the handles visually.
cubic-bezier.com: real-time preview with a side-by-side comparison.
Tip: always test a curve at 0.25× speed in DevTools — slow motion reveals even the smallest artifacts.
Common mistakes
Using linear for a loading animation: linear looks mechanical. Use ease-in-out for shimmer/pulse.
Duration too long with ease-out: ease-out 0.8s looks like lag. Usually 150–300ms is enough for a UI element.
Using the same curve for enter and exit: enter should be ease-out (fast in, slow settle). Exit should be ease-in (slow start, fast disappearance) — this matches how the human eye tracks motion.
See how these principles apply in practice: Hover Micromotion, Scroll Reveal Patterns, Page Transition Patterns.