Motion that means something

Design

Every transition in Kool answers a question: where did this come from, and where did it go. Animation that does not answer one of those gets cut.

In practice that means motion is a small, named vocabulary rather than a per-screen decision. There are five motion tokens in design/tokens.json and they cover everything. koolSpring, a 0.5 response at 0.85 damping, is the default for anything arriving or leaving. koolSnap is the same shape at 0.35, for things that should feel decided rather than gentle. koolFade is a quarter-second ease-out. headerFade, two tenths, is specifically the frosted band appearing as content passes under the header. resizingSheetSettle is 0.32, and its documentation is a warning: only the present and dismiss of a resizing sheet are animated, because the live drag is driven frame by frame and must not be animated on top of. Animating a gesture that is already tracking a finger is how you get a sheet that lags behind the thumb holding it.

Three of the five carry a reduced-motion variant in the same object, which is the point: the preference is answered at the token rather than at every call site. The header fade and the sheet settle do not carry one, and that is a gap rather than a decision. koolSpring under the preference is not a weaker spring, it is a two-tenths ease-out tween. The substitution happens at the token, so a screen cannot forget to handle it, and nobody has to remember which animations were the dangerous ones.

That is the easy half. The hard half is the motion that is not an animation at all.

Our features page is built from scroll-linked scenes: a camera moving through a field of tiles, a card growing to fill the viewport, a ring of panels the viewer travels out of. For a long time we exempted all of it from the reduced-motion preference, with what sounded like a good argument. Nothing is autoplaying. The reader owns every frame. Stop scrolling and the picture stops.

That argument survives right up until you name the things it excuses. It was excusing a word growing from 72 pixels to the full height of the screen, a card expanding to full bleed, and a camera dolly of 1520 pixels. Controlling the rate of a zoom is not the same as not being zoomed, and the preference is about the zoom. The distinction we had drawn, autoplay against scroll-linked, is a real distinction. It is just not the one being asked about.

So those scenes now cut and crossfade instead of scaling. The zoom-through is already the size it ends at and fades in over the same stretch of scroll. The mission row moves between its three states rather than through them. The dolly is quantised to its two ends, and that last one is the detail that matters most: we kept both states rather than freezing at one, because one of them is the frame where the panels are full-bleed and readable, and dropping it would have removed the content along with the movement.

Every word, every state and every image is still reachable by scrolling. Only the travel between them is gone. That is the line we hold: reduce the motion, never the page.

All posts