Two animations with the same duration can feel completely different. Duration decides how long something takes; the easing curve decides what it feels like on the way. Most motion that reads as "cheap" isn't too slow or too fast — it's linear.

Nothing in the physical world moves linearly. Things accelerate, coast, and settle. When an element glides at constant speed, our eyes flag it as synthetic immediately.

Start with ease-out

For elements entering the screen, you almost always want an ease-out curve — fast at the start, settling at the end. The interface responds instantly, then calms down:

.menu {
  transform-origin: top;
  transition: transform 180ms cubic-bezier(0.16, 1, 0.3, 1),
    opacity 180ms ease-out;
}
 
.menu[data-closed] {
  transform: scaleY(0.95);
  opacity: 0;
}

The curve cubic-bezier(0.16, 1, 0.3, 1) overshoots nothing but decelerates hard, which makes a 180ms animation feel even faster than it is.

State changes want symmetry broken

A common mistake is using one curve everywhere. Enter and exit have different jobs: an entrance should announce itself, an exit should disappear before you think about it. In practice that means exits get shorter durations and simpler curves:

function toggle(menu, open) {
  menu.style.transition = open
    ? "transform 180ms cubic-bezier(0.16, 1, 0.3, 1)"
    : "transform 120ms ease-in";
  menu.dataset.state = open ? "open" : "closed";
}

Feel it, don't read it

Curves are impossible to evaluate in a table of numbers. Put the interaction in your hand and press it a few dozen times — a morphing control makes the character of a curve obvious in a way no cheatsheet can.

When a transition finally feels right, write the values down. A small set of named curves you actually trust beats a folder of bookmarked easing galleries.