DEV Community

Sherotree
Sherotree

Posted on

CSS Easing Explained: Picking the Right cubic-bezier() for Enter, Exit and Move Animations

For a long time I wrote transition: all 0.3s ease on everything and moved on. It worked fine. It also made every interface I built feel a little bit off, and I couldn't explain why until I started paying attention to easing.

This post is the mental model I use now. If you only want the values, I keep the full list of curves as CSS variables in a gist: CSS cubic-bezier() Easing Cheat Sheet.

Why easing matters at all

Nothing in the real world moves at a constant speed. A drawer you pull out speeds up and then slows to a stop. A ball you drop speeds up until it hits the floor. When a UI element moves linearly, your brain notices that it's "wrong" even if you can't say why.

Easing is how you describe that change in speed. In CSS, every timing function is really a cubic-bezier(x1, y1, x2, y2). The x axis is time and the y axis is progress, both from 0 to 1. Even the keywords are just presets: ease is cubic-bezier(0.25, 0.1, 0.25, 1) and ease-in-out is cubic-bezier(0.42, 0, 0.58, 1).

The most useful way to read a curve: a steep part means fast, a flat part means slow. That's really all there is to it.

Enter, exit, move

I use three rules and they cover about 90% of what I build.

Things entering the screen: ease-out. A modal, a toast, a dropdown. It should show up fast and settle gently. Ease-out puts the speed at the start, so the element looks like it reacts to the user right away and then slows down where the eye lands on it. My default is cubic-bezier(0.33, 1, 0.68, 1) (easeOutCubic). When I want something snappier, I use easeOutQuint, cubic-bezier(0.22, 1, 0.36, 1).

Things leaving: ease-in. Same logic in reverse. The element starts slowly and speeds up as it goes away, which reads as "it's gone." Exit animations should also be a bit shorter than enters. Nobody wants to watch something leave.

Things moving from A to B on screen: ease-in-out. A tab indicator sliding, a card changing position, a carousel. Both ends are visible, so the element should speed up and slow down: cubic-bezier(0.65, 0, 0.35, 1) (easeInOutCubic).

One thing I avoid is plain ease-in for enter animations. It feels laggy, because the slowest part happens right when the user is waiting for feedback.

.toast {
  transition: transform 250ms cubic-bezier(0.33, 1, 0.68, 1),
              opacity 250ms cubic-bezier(0.33, 1, 0.68, 1);
}
.toast[data-leaving] {
  transition-duration: 180ms;
  transition-timing-function: cubic-bezier(0.32, 0, 0.67, 0); /* easeInCubic */
}
Enter fullscreen mode Exit fullscreen mode

Durations

Easing and duration go together. A good curve at the wrong duration still feels bad. These are the ranges I start from:

  • Hover, press, small color changes: 100–200ms
  • Dropdowns, tooltips, toasts: 150–250ms
  • Modals, drawers, page sections: 250–400ms
  • Big full-screen transitions: up to about 500ms

Bigger elements and longer distances can take a bit longer. On mobile, I go a little shorter than on desktop. If something feels slow, I almost always fix it by cutting the duration, not by changing the curve.

Also, stop using transition: all. List the properties (transform, opacity). It's cheaper to render, and you won't accidentally animate layout properties like width or top.

Overshoot with Back curves

cubic-bezier() lets the y values go outside 0–1 (the x values can't). If y goes above 1, the element shoots past its final value and settles back. That's the "Back" family:

/* easeOutBack: overshoots ~10% then settles */
.badge-pop { transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1); }
Enter fullscreen mode Exit fullscreen mode

I like this on small playful things: a notification badge popping in, a like button, a checkbox tick. I'd never use it on a modal or anything large. On a big surface, a wobble feels sloppy instead of fun. A real bounce or elastic effect, with several wobbles, can't be done with one cubic-bezier. For that you need @keyframes or the newer linear() function.

Respect reduced motion

Some people get dizzy or nauseous from motion on screen, and they turn on "reduce motion" in their OS. Honoring that setting takes a few lines:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
Enter fullscreen mode Exit fullscreen mode

I use 0.01ms instead of none so that transitionend and animationend events still fire and JS that waits on them doesn't break. A softer approach is to keep opacity fades and drop only the transforms (sliding, scaling, parallax). Those cause most of the trouble.

Keep curves in one place

My last tip is to put your curves in CSS custom properties and use only those:

:root {
  --ease-out: cubic-bezier(0.33, 1, 0.68, 1);
  --ease-in: cubic-bezier(0.32, 0, 0.67, 0);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
}
Enter fullscreen mode Exit fullscreen mode

Three or four named curves applied consistently do more for how an app "feels" than a dozen hand-tuned ones. The gist has the full Sine through Back set if you want more options.

When I need a custom curve, I drag the handles in a visual editor until it looks right, then copy the four numbers. Lately I've been using this cubic-bezier editor. It runs in the browser and doesn't need an account. Seeing the curve beats guessing numbers every time.

That's it: ease-out to enter, ease-in to leave, ease-in-out to move, keep it short, and respect reduced motion.

Top comments (1)

Collapse
 
johnnylemonny profile image
𝗝𝗼𝗡𝗻 •

Great article! I like that you focused on the intent behind different animations rather than treating cubic-bezier() values as random numbers to copy and paste. The distinction between enter, exit, and move animations makes a lot of sense, and it's a good reminder that motion is part of the user experience, not just decoration. Small easing adjustments can make an interface feel surprisingly more polished and responsive. Thanks for the clear explanations and examples! πŸ‘βœ¨