I Stopped Using ease-in-out Everywhere: A Practical Guide to CSS Easing
Most of my early CSS transitions looked something like this: .button { transition: transform 300ms ease-in-out; } Hover state? ease-in-out. Modal? ease-in-out. Card animation? You guessed it: ease-in-out.
Most of my early CSS transitions looked something like this:
.button {
transition: transform 300ms ease-in-out;
}
Hover state?
ease-in-out.
Modal?
ease-in-out.
Card animation?
You guessed it: ease-in-out.
It worked, so I didn't think much about it.
But while paying more attention to motion in interfaces, I started noticing something:
Two animations can have the same duration and move the same distance, yet feel completely different.
One feels responsive. Another feels heavy.
One feels smooth. Another feels strangely robotic.
The difference is often not the property being animated.
It's the easing.
Duration controls how long. Easing controls how it feels.
Consider these two cards:
.card-a {
transition: transform 300ms linear;
}
.card-b {
transition: transform 300ms ease-out;
}
Both transitions take exactly 300ms.
But their movement doesn't feel the same.
linear moves at a constant rate.
ease-out starts quickly and slows down toward the end.
That difference matters because UI motion isn't just decoration. It communicates how an interface is responding to the user.
A hover interaction usually needs to feel immediate.
An element entering the screen may feel better when it arrives quickly and settles smoothly.
A playful interaction might benefit from a small overshoot.
Using the same easing function for every interaction ignores those differences.
The built-in keywords are already BΓ©zier curves
CSS gives us several familiar easing keywords:
linear
ease
ease-in
ease-out
ease-in-out
Except for linear, the familiar easing keywords map to cubic BΓ©zier curves.
For example:
ease-in-out
is equivalent to:
cubic-bezier(0.42, 0, 0.58, 1)
And:
ease-out
maps to:
cubic-bezier(0, 0, 0.58, 1)
This was the point where cubic-bezier() started making more sense to me.
It isn't a completely separate animation system.
It's a way of controlling the shape of the easing curve directly.
A practical mental model for cubic-bezier()
The syntax looks like this:
cubic-bezier(x1, y1, x2, y2)
The curve starts at (0, 0) and ends at (1, 1).
Those endpoints are fixed.
The four values define two control points that influence the shape of the curve between the start and end.
A simple way to think about the graph:
- The x-axis represents input progress through the animation.
- The y-axis represents output progress.
You don't need to calculate the curve manually to use it effectively.
For day-to-day frontend work, I find it more useful to ask:
- Should the animation start immediately?
- Should it slow down gently?
- Should it briefly overshoot?
- Should it anticipate movement by pulling back first?
Then I test the curve visually against the actual component.
Four numbers alone don't tell the full story. The animation needs context.
Three useful motion patterns
Instead of memorizing dozens of curves, I find it more useful to understand a few different motion intentions.
1. Snappy: fast response, smooth finish
For an interface element that should respond quickly and settle smoothly:
.button {
transition:
transform 250ms cubic-bezier(0.16, 1, 0.3, 1);
}
.button:hover {
transform: translateY(-2px);
}
This kind of curve can work well when an interaction needs to feel immediate without stopping abruptly.
I wouldn't blindly use this exact curve everywhere.
The important part is the intention: fast response, smooth finish.
2. Overshoot: move past the target, then settle
One useful detail about cubic-bezier() is that the y values aren't limited to the 0β1 range.
That means a curve can temporarily move beyond the final progress value.
For example:
.toast {
transition:
transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
The value 1.56 allows the progress to move beyond the target before settling back.
That can create an overshoot effect.
Used carefully, it can work for:
- a toast appearing,
- a small confirmation interaction,
- a playful button,
- or a modal entrance.
The key phrase here is: used carefully.
Overshoot on every component quickly becomes distracting.
3. Anticipation: pull back before moving forward
You can also use a negative y value:
.playful-element {
transition:
transform 500ms cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
This can create motion that pulls back slightly before moving forward, then overshoots before settling.
It's expressive.
It's also very easy to overuse.
I would reserve motion like this for interfaces where playful movement matches the product's visual language.
A banking dashboard probably doesn't need every card bouncing into place.
A game, creative tool, or playful onboarding experience might.
The same curve can feel completely different at different durations
This was another useful lesson for me:
Don't evaluate an easing curve separately from its duration.
Consider this:
.element {
transition:
transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
Now compare it with:
.element {
transition:
transform 700ms cubic-bezier(0.16, 1, 0.3, 1);
}
The easing curve is identical.
The experience isn't.
A curve that feels responsive at 200ms can feel exaggerated or sluggish at a much longer duration.
That's why copying a collection of "best cubic BΓ©zier curves" isn't enough.
The curve needs to be tested with:
- the actual element,
- the actual distance,
- the actual property,
- and the actual duration.
Context changes motion.
How I read easing curves without memorizing the math
I don't try to memorize dozens of BΓ©zier values.
Instead, I use a rough mental model.
Slow start, faster finish
Think of an ease-in shape.
The motion takes time to get going and accelerates later.
This can work when an element is leaving the interface, although the correct choice always depends on the interaction.
Fast start, gentle finish
Think of an ease-out shape.
The element responds quickly, then slows as it reaches its destination.
This often feels natural for elements entering the interface.
Slow β fast β slow
That's the familiar ease-in-out pattern.
It can work well for movement that has a clear beginning and end, but it isn't automatically the right choice for every UI interaction.
Values outside the normal y range
Think:
- overshoot,
- anticipation,
- or more expressive motion.
The easiest way to understand these differences is still to see the curves moving.
That's why I built a visual CSS Shapes & Animation Playground on DevStash. I wanted to experiment with easing as motion rather than stare at four numbers.
When cubic-bezier() isn't the right tool
cubic-bezier() is powerful, but it isn't a complete motion system.
A single cubic BΓ©zier gives you one continuous easing curve for the transition.
It can handle:
- acceleration,
- deceleration,
- anticipation,
- and overshoot.
But it can't model a true physics-based spring that dynamically responds to velocity or interruption.
For more complex motion, you might consider:
- CSS keyframes,
- the Web Animations API,
-
linear()with multiple easing stops, - or an animation library that provides spring physics.
The goal isn't to force every animation into cubic-bezier(). The goal is to understand the motion problem before choosing the tool.
What changed in the way I write UI motion
I still use the built-in easing keywords.
There is nothing wrong with them.
The difference is that I no longer choose one automatically just because it's familiar.
Before adding motion, I now ask:
- What should this interaction feel like?
- Should it respond immediately?
- Should it arrive softly?
- Should it feel playful?
- Should the user barely notice the animation at all?
Those questions are more useful than asking:
Which easing function is the best?
There is no universally best curve.
There is only a curve that fits β or doesn't fit β the interaction you're designing.
Want to explore the curves visually?
I originally explored this topic as a visual guide on DevStash, where I go deeper into:
- how the four control-point values work,
- the exact values behind built-in easing keywords,
- overshoot and anticipation,
- practical curve examples,
- and experimenting with easing visually.
π Read the complete visual guide on DevStash:
https://devstash.me/blog/understanding-css-cubic-bezier-easing
If you work with UI motion, I'm curious:
Do you choose easing intentionally, or do you still find yourself reaching for
easeorease-in-outby default?
Let me know in the comments.
Sources and further reading
- MDN Web Docs β
cubic-bezier() - CSS Easing Functions Module β W3C / CSS Working Group
- Chrome for Developers β Create complex animation curves in CSS with
linear()
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.
