Easing curves
beginner·7 min·Markdown for agents
What this lesson teaches
Pick the right easing curve for anything that moves, and know why ease-out makes the same animation feel faster.
Open both dropdowns at once. They take exactly the same 250 milliseconds. One of them feels sluggish.
Same duration on both sides. The right one feels faster because it moves the moment you click.
The difference is the : how the speed changes during the animation. The left one uses , which starts slowly and speeds up. The right one uses , which moves most of the way at once and spends the rest of its time settling.
When you click, your eye goes straight to where the menu should appear. Ease-out puts something there in the very first frames. Ease-in makes you watch almost nothing happen, right at the moment you're paying the most attention. Never use ease-in on UI, exits included.
The decision order
You don't need to memorise curves. You need one rule per situation:
| What's happening | Curve |
|---|---|
| Something entering or leaving (menu, toast, modal) | ease-out |
| Something already on screen moving from A to B (a tab indicator) | ease-in-out |
| A hover or a color change | ease |
| Constant motion: a progress bar, a marquee, a hold-to-confirm fill | linear |
| Not sure | ease-out |
A drag, a flick or anything your finger is steering doesn't use a curve at all. It uses a spring, which gets its own lesson.
Custom curves beat the built-ins
CSS ships ease, ease-in, ease-out and ease-in-out. They were fine defaults a decade ago; today they read as "nobody chose this". A lets you write your own curve as four numbers, and a slightly stronger curve is most of what makes motion feel deliberate.
cubic-bezier(0.23, 1, 0.32, 1)Anything entering or leaving: dropdowns, popovers, toasts, modals.
Here is a small set worth keeping as tokens, mostly from Emil Kowalski's practice:
:root {
--ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* menus, popovers, toasts, modals */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* on-screen travel, tab indicators */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); /* sheets and drawers from an edge */
--ease-out-soft: cubic-bezier(0.25, 1, 0.5, 1); /* gentler hovers and menus */
--ease-back: cubic-bezier(0.34, 1.56, 0.64, 1); /* overshoots: playful moments only */
}Read the curve like a graph: time runs left to right, progress bottom to top. A steep start means it moves early. A number above 1 (like the 1.56 in back-out) means it shoots past the end and settles back, which is cute on a sticker and wrong on a delete dialog.
Curve and duration go together
A beautiful curve on the wrong duration looks worse than a plain ease-out on the right one. Tune them as a pair, which is the next lesson. Need a curve that isn't on the list? Take one from easing.dev or easings.co rather than hand-rolling four numbers.
✕ Before
.menu {
transition: all 300ms ease-in;
}✓ After
.menu {
transition:
opacity 200ms var(--ease-out),
transform 200ms var(--ease-out);
}Three fixes in one line: ease-out instead of ease-in, a named list of properties instead of all, and a shorter duration.
A note on writing it down
Small conventions make motion CSS easier to read and review. They come from Ben DC's frontend guidelines:
- Pick one time unit and stick to it. This course uses milliseconds (
200ms) everywhere, because they match JavaScript timers and design tokens. Mixing200msand0.2sin one file is the actual bug. - Rotate in turns.
rotate(0.25turn)is a quarter turn,rotate(1turn)a full spin. Keep degrees for arbitrary angles like14deg. - Unitless line-heights,
remfor things that should scale with the reader's font size andpxfor hairlines. - Avoid
!important; it usually means a specificity fight the cascade should settle.
When you join an existing codebase, its conventions win. These are for when you're starting fresh.
Audit the CSS transitions and animations in this project. Replace any ease-in on UI with ease-out. Use these tokens: --ease-out: cubic-bezier(0.23, 1, 0.32, 1) for things entering or leaving, --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1) for things moving across the screen, --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1) for sheets. Use linear only for progress. Name the properties in every transition instead of "all", and keep durations in milliseconds.
Quick check
A dropdown uses transition: opacity 250ms ease-in. A user says it feels slow. What's the best first fix?
In the skill
The nodes your agent reads when it does this work.
Connected lessons
What this lesson builds on and leads to, across every course.