Leave faster than you arrive
The internal tools I build at work have almost no animation, on purpose. What they do have follows one rule: things arrive on a deceleration and leave faster. Arriving is news, so it gets a moment. Leaving is you being done with something, so it gets out of the way.
The whole motion system is four durations and two curves:
--m-fast: 120ms; /* colour, presses */
--m: 200ms; /* chevrons, carets */
--m-slow: 280ms; /* toasts, drawers arriving */
--m-focal:560ms; /* the one authored moment */
--ease-out: cubic-bezier(.16, 1, .3, 1); /* arriving */
--ease-exit: cubic-bezier(.4, 0, 1, 1); /* leaving */
Buttons and chips shrink to 0.97 when pressed, bigger targets like cards only to 0.985, since 3% of something big is a lot of pixels.
Toasts
A toast slides up 8px and fades in over 280ms on the ease-out. When it goes, it doesn't play that backwards: it drops 4px and fades over 180ms on the exit curve, which starts slow and accelerates away. Press both buttons a few times and you'll feel the difference before you can name it. The symmetric one hangs around like a guest who has said goodbye twice.
There's only ever one toast element, with role="status", reused for every message so screen readers announce it and nothing stacks. The exit is a Web Animations call, so it can hide the element when it finishes. One trap I only noticed while rebuilding it for this page: the real toast is centred with translateX(-50%) on itself, and an animation that sets transform replaces that, so it drifts sideways as it comes and goes. Here the centring lives on a wrapper, and the fix is going back to work with me.
Drawers and themes
On a phone the navigation is a drawer: 280ms in on the ease-out, 180ms out on the exit curve, with the backdrop fading on the same split. Switching theme crossfades the whole page in 220ms with a View Transition, so nothing flashes.
The bar that never finishes
When you click a link and the next page takes a moment, a 2px bar crawls along the top. It heads for 85% over six seconds on cubic-bezier(.1, .7, .2, 1), which gets two-thirds of the way there in the first second and then slows to a creep. It never claims to be done. When the page arrives the bar just fades out over 300ms while retracting, and if nothing arrives in twelve seconds (a download, an error) it gives up on its own.
A clock that goes negative
One of the tools has a presenter mode for giving a five-minute version of a story: six frames, arrow keys, a script you can toggle, and a timer counting down from 5:00. The detail I like is what happens at zero. It doesn't stop or beep; it keeps going, prints a plus sign, +0:12, and changes colour. You're over, it says, by this much. That's all. Each new frame rises 6px over 280ms, so moving forward feels like turning a page rather than a cut. This one starts at 0:10 so you don't have to wait.
Being pointed at
Long pages have an "On this page" bar. Click an entry and the page scrolls there, and then the section briefly tints: an inset box-shadow with a 100vmax spread, which fills the box with 9% of the accent and fades over 1.1 seconds. It waits 220ms first, so the tint happens where you're looking, after the scroll lands. Click the same entry twice and it flashes twice, because the class is removed, the layout is read with void el.offsetWidth, and the class goes back on, which restarts the animation.
Brief
Numbers
Risks
Next steps
With reduced motion on, nothing travels or scales: the toast just fades in 150ms, the drawer snaps, the flash is skipped. Colour changes stay, so feedback still reads.
- Built
- October to November 2026
- Stack
- CSS custom properties, Web Animations, View Transitions
- This page
- Vanilla JS and CSS
- Lives at
- Internal tools at work
- Source
- Private