Jul 16 2025 · 5 min read

The subtlety of animations

We used to make buttons breathe

A decade ago, the buttons in a polished web app lingered. Four hundred milliseconds of ease-out, a little swell as the colour settled, a state change you could almost feel land. That was deliberate. The animation was the proof that someone had cared enough to add it, and I built plenty of them myself. At the time they felt like the whole difference between an app and a nice app.

Now the same animation reads as drag. A control I press forty times an hour can't ask me for four hundred milliseconds each time, because I feel every one of them stacking up over a day. Nothing about the curve got worse. What changed is how often I have to sit through it.

Frequency is the lever

Whenever I'm about to put a number on a transition, the first thing I try to work out is how often someone is going to see it. That one question does most of the work for me.

A modal that opens once or twice a session can take three hundred milliseconds and feel considered. A button somebody hits forty times an hour wants to be down around a hundred, or the interface starts to feel like it's wading through treacle. And an action fired from a keyboard shortcut should usually take zero. Animating something the user triggered from the keyboard puts a layer of lag between their intent and the result, and the whole thing feels oddly disconnected from their hands.

Raycast is the example I always come back to. The command palette has no open or close animation at all. You press the shortcut and it's there, you press Escape and it's gone. For something people open hundreds of times a day, that's exactly right. The animation budget isn't a fixed amount of polish you sprinkle evenly over every element. It's something you spend where the user lands rarely, and withhold where they land constantly.

Easing is where the character lives

If duration is the quantity, the easing curve is the tone of voice. It's the difference between an interaction that feels confident and one that feels sluggish, even at the exact same speed.

ease-in starts slow and accelerates, and on anything entering the screen it's a quiet disaster: the exact moment the user is watching most closely is the moment nothing appears to happen. A dropdown on ease-in at 200ms genuinely feels slower than the same dropdown on ease-out at 200ms, even though the number is identical, because one of them answers you straight away and the other makes you wait for it.

ease-out is the sensible default for things coming in, and the browser's built-in version is perfectly fine. But once you've felt cubic-bezier(0.23, 1, 0.32, 1) — a sharper entry with a long, soft tail — the default starts to look a little flat. Two others I keep within reach: cubic-bezier(0.77, 0, 0.175, 1) for something travelling across the screen that needs to speed up and then settle, and cubic-bezier(0.32, 0.72, 0, 1) for drawers and modals, where the motion should carry a bit of weight, like a heavy panel sliding rather than a graphic snapping into place.

Knowing when not to

The hardest part of this isn't picking good curves. It's resisting the urge to animate things that were perfectly fine sitting still.

The list doesn't need to fade in when the page loads. The button doesn't need to bounce. The page doesn't need to swoop up from the bottom. Most of these exist because someone (often me, a few years ago) felt that the absence of motion read as a missing feature, as though a still interface was an unfinished one. It isn't. A list that simply appears, fully formed, the instant the data is ready is calmer and quicker to use than one that staggers itself in over six hundred milliseconds. That stagger looks like care the first time you see it. By the tenth page load, you just want your list.

Sonner gets this right. Toasts arrive without ceremony, stack neatly, and dismiss with a swipe. The library could easily have made the entrance more theatrical, and the restraint is a big part of why people reach for it. The same logic holds inside a single component. If a button has a hover state and an active state and an entrance and an exit, each animated on its own little timeline, the sum of them is noise. I'd usually keep one: the small scale(0.97) on :active that makes the thing feel like it actually registered the press. The rest can hold still.

Why I think this matters

I don't think design engineers ended up caring about animation because the field fell in love with motion. If anything it's the opposite. We fell in love with restraint, and motion just happens to be the place restraint shows up most clearly. The durations someone chose on purpose, the curves that match the weight of the thing they're moving, the entrances they decided not to build, none of it announces itself. Nobody walks away praising the dropdown that didn't delay them or the modal that arrived feeling exactly as heavy as it should. They just come away with a sense that the whole thing was built by someone who was paying attention. That's the part I'm chasing, most days.