An animation only feels natural once its timing is right. Easing curves determine how a motion accelerates and slows down over time. This guide explains the most important curves and how to use them in React and CSS.
- Without easing every motion feels mechanical, because in the real world nothing starts and stops at the same speed.
- Ease-out for anything entering. Ease-in for anything leaving. Ease-in-out for motions with a beginning and an end.
- The same curve belongs in CSS and in JavaScript, otherwise a site feels inconsistent.
- Short durations feel more refined than long ones. 0.3 to 0.8 seconds is the useful range.
What easing means
Without easing an element moves linearly, at a constant speed. That feels mechanical. In the real world things accelerate and slow down. That is exactly what an easing curve reproduces.
Technically a cubic-bezier curve describes four values that control the path between start and end. It can be drawn as a curve inside a unit square.
- Value for CSS
- cubic-bezier(0.22, 1, 0.36, 1)
- Value for framer-motion
- [0.22, 1, 0.36, 1]
Four curves to switch between. Below, two dots run the same distance, once with a curve and once without.
In, Out and InOut
Ease-In starts slow and speeds up. Good for motions that leave the frame. Ease-Out starts fast and eases out softly. Ideal for elements that enter the frame, because the ending feels calm.
Ease-InOut is symmetric: slow at the start and end, fast in the middle. Suitable for motions that begin and end again, such as a panel that opens and closes.
/* kommt herein */ transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1); /* geht hinaus */transition-timing-function: cubic-bezier(0.55, 0, 1, 0.45); /* auf und zu */transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- Ease-in for a menu opening. It starts sluggish and snaps shut at the end.
- A different curve for every motion, because a different one felt nice at the time.
- Linear for everything, because it is the browser default.
- Ease-out for anything appearing. The start is fast, the ending calm.
- Ease-in only for leaving. Whatever goes away may accelerate.
- Ease-in-out for states with a there and back, such as a panel.
Curves in framer-motion
In framer-motion you pass the curve as a tuple, for example ease: [0.22, 1, 0.36, 1] in the transition. This curve eases out especially softly and suits most fade-ins.
Keep durations short. Between 0.3 and 0.8 seconds most UI motions feel pleasant. Longer times quickly feel sluggish.
The same curve, three durations. The useful range is narrower than it feels while building.
const ease = [0.22, 1, 0.36, 1] as const; <motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease }}/>
Three curves cover everyday work.
A page with three curves feels ordered, one with twelve feels random. These three are almost always enough.
Curves in CSS
In CSS you use the same value as transition-timing-function, for example cubic-bezier(0.22, 1, 0.36, 1). This way CSS transitions and JavaScript animations behave consistently.
A curated collection of curves with preview and copy function is available in the library's resources.
:root { --ease-soft: cubic-bezier(0.22, 1, 0.36, 1);} .card { transition: transform 0.5s var(--ease-soft);}
Curves that almost always work
You do not need a collection of forty curves. Three cover everyday work, and you fetch the rest when a motion really has to be different.
- 0.22, 1, 0.36, 1 Eases out very softly. The default choice for fade-ins, cards and anything entering.
- 0.4, 0, 0.2, 1 More symmetric and a bit tighter. Good for panels, menus and states that open and close.
- 0.32, 0.72, 0, 1 Starts briskly and arrives very calmly. Pleasant for long distances, such as a change across half the page.
More important than the exact numbers is using the same curve everywhere. A page with three curves feels ordered, one with twelve feels random.
A motion you notice is usually too long, not too weak.
When a spring fits better
An easing curve has a fixed duration. That is good for fades, but poor for anything reacting to input: an element following the pointer does not know its target yet.
That is what springs are for. In framer-motion you set type: "spring" with stiffness, damping and mass. High stiffness feels fast, high damping feels calm. For subtle motion, low stiffness with high damping is the safe choice.
transition={{ type: "spring", stiffness: 120, damping: 20, mass: 0.6,}}
A curve when you know the target. A spring when the target comes from input.




