Foundations

Motion

One duration, one cycle, two curves, and thirteen components that use them. This page is what each of them is for, and every example on it is the real component rather than a description of one: point at things, move through them with a tab key, and read the numbers the browser gives back.

Nothing in this product moved before this stage, and that is measured rather than remembered.

The census read 122,458 elements over 282 renderings, every page of the product and of this stand, at two widths, and compared it against a search over all four corpora: zero transitions, zero keyframes, zero animations. Source and output agreed, which is the only condition under which agreeing means anything. So nothing here is the survivor of a drift, and every value below is a decision.

reading your own setting

Three works, and there is no fourth

A movement earns its place by doing one of exactly three jobs. A moment for which none of the three can be named does not get animated, and that is not an impoverishment: it is a band of noise taken off a surface somebody reads for six hours.

connection

Where did that come from?

Something appeared and the movement says what it appeared out of. A panel slides from under the control that opened it, and closes back into it.

One moment in this product, and it got no motion. Almost every appearance here is a document navigation: 66 screens, each its own file. The one that is not, the help on the sign in page, can only open by changing a height, and a height is the property this stage forbids.

status

Is it still working?

A process is running and the movement says so without a sentence. Taken only where the process is longer than about 300ms, because a flicker on a fast answer is worse than stillness.

One moment, and the product reserved it two stages ago. The queue's filling bar. Its own file wrote down that it was leaving the animation out and which stage would owe it.

response

Did it hear me?

The machine noticed. Hover, press, focus. Faster than about 150ms or it has stopped being a response and become a lag.

Thirteen components, and one global focus ring. This is nearly all the motion in this product, and it is exactly the set of states stage 08 declared: the movement was laid on the states rather than inventing any.

Response, and it is the whole of the everyday

Point at these. Then move through them with a tab key, which is the way this product is meant to be used at all: design principle 5 puts keyboard before mouse.

the real components, at the real duration
Accept a Amend m Okta system log Shift

Every one of them takes 120ms and the browser's own ease. What changes is a ground, a boundary, an ink or an underline, and never a size or a position: those make the browser recalculate the layout of the page on every frame, and this surface also holds two hundred rows.

the same components, slowed to two seconds so the movement is visible
Accept a Amend m Okta system log

Nothing in these components was changed to do that. One custom property is redefined on the box around them, and every component inside obeys because it reads var(--dur-fast) rather than a number. That is the same mechanism the whole "less motion" answer below is built on, shown here doing something harmless.

And this box broke two things on its first run, which is worth leaving written down. Redefining the token on a class beats the reduced motion block, which redefines it on the root: for one measurement, the one demonstration on this page that exists to show the override reaching everything was the only thing in the project it did not reach. It carries its own query now, and any surface that redefines a motion token has to.

The second was better. A response in this product is a colour transition, so a change of theme is a change of colour, so switching the theme with two seconds in force makes every component in here spend two seconds crossing from one ground to the other. The contrast sweep caught three of them mid crossing at 3.64:1 against a floor of 4.5, on a page that had been clean a minute before. At 120ms the same crossing happens and nobody can read anything in 120ms. This box is slow only while somebody is inside it.

The focus ring does not transition, and that is a decision rather than an oversight. It is the one response whose result cannot be seen until it has finished: somebody moving by keyboard does not know where they are until the ring is there, so 120ms of fading in is 120ms of not knowing. The rule that a response slower than about 150ms stops being a response is at its strictest here, and the strictest reading of it is zero.

Status, and there is one

the queue is still filling

Two pixels of bar. It sweeps rather than pulsing, and the difference is what the bar claims: a pulse in opacity says a thing is present and uncertain, a sweep says work is moving through. The fill stays at the 40 per cent the component committed to two stages ago and only its position changes, because transform is the one property that costs the browser neither layout nor paint.

Never a spinner and never a skeleton of the queue. A spinner says the product is thinking and tells the analyst nothing; a skeleton draws eighteen rows that do not exist, on a surface where a row is a decision.

The nearest living relative of this bar animates nothing, and that is where its character came from. The evaluation gauge on lichess.org, which research.md named at stage 01 as one of four out-of-category benchmarks for calibrated trust in a machine, was measured in session: it carries no CSS motion at all and is simply redrawn. So the character here is derived from the work and from the tone rather than from a source, and the source is recorded as having said nothing.

The register

TokenValueWorkWhere it came from
--dur-fast120msresponseA response slower than about 150ms is a lag. The live benchmark measured in session writes 150ms and ease on every one of its controls; this product is denser and read for six hours, so it takes one step quicker rather than copying the number
--dur-cycle1.4sstatusThe time the filling bar takes to cross once. A period rather than a duration, and a token for the same reason: it was a literal for exactly as long as it took to run the check that reads every duration in the product against this table
--ease-standardeasebothThe browser's own, and it is what the benchmark measured. A curve nobody can name is a decision nobody can check
--ease-enterease-outarrivingQuick to start, long to settle
--move-sm4pxanyA movement longer than about one and a half times the height of the thing moving stops reading as a connection and starts reading as a journey

There is no --dur-base, and the reason is the finding rather than a gap. It was written first, for the one moment of connection the inventory found. It has no reader, because that moment turned out to have nothing cheap to move: the only property a details can open with is a height. A token with no reader fails this system's own idle control, so it is not carried for later.

No spring and no bounce anywhere. They read as "something went wrong" in exactly the states where the analyst least wants to be asked a question, and the tone table in voice.md has no row a spring would satisfy.

Less motion, and it is not an option

For a large minority this is nausea and migraine rather than a preference. The mechanism is one thing: the tokens are redefined.

@media (prefers-reduced-motion: reduce){ :root{ --dur-fast: 1ms; --move-sm: 0px } }

A component that reads var() obeys without knowing this block exists, and so does a component written at stage 12 that nobody has thought of yet. That is the whole argument for an override rather than a list of exceptions, and it is why this block was written at the same step as the tokens rather than at the end.

1ms rather than 0s, and the difference is the check rather than the eye. A transition of exactly zero and a transition that was never declared read identically in computed style, so a suite asking for zero cannot tell an element that obeyed from an element nobody asked. The check fails on anything above 1ms.

Reducing motion removes the movement and never the thing. An element that appears normally and is simply absent under reduce is a worse defect than any amount of extra animation, and the check measures that separately, against what renders rather than against what moves.

And three things the override cannot reach.

A cycle. Making a pulse 1ms does not calm it, it turns it into a strobe. The one cycle in this product replaces itself with a still state, in its own file, under its own query: the bar keeps the 40 per cent fill it carried for three stages and says exactly what it said before.

A movement between documents. A view transition has its own default animation, owned by the browser, and no custom property of ours is in it. It is turned off explicitly in base.css, beside the declaration that turned it on.

A literal inside a @keyframes block. There is none, and the check cannot see one if there ever is: it reads computed style, and a keyframe's own values never appear there. It is closed by search instead, and that is why it is written down here.

Between documents, and it is the product's main move

Every state of every screen here is its own html file. So the single most important movement the analyst makes, a row in the queue becoming a case in the pane, is a navigation, and a navigation cannot be a transition. That is not a limitation of the prototype: a case has its own address by decision, because somebody opens one from a pager at 03:00.

Three answers were legal and the middle one was taken. Not animating between documents at all leaves the product's main move with no connection. A decision per flow buys nothing, because every flow here is the same move. The middle one is a single declaration, @view-transition{ navigation: auto } in base.css, and the browser cross fades the old document into the new.

It degrades to nothing, loudly enough to be honest. A browser without view transitions ignores the at-rule and navigates exactly as it did before: no error, no fallback to write, no half working promise. That property is what made this answer legal rather than a guess. Move between two screens of the product to see it, or not see it, depending on what you are reading this in: the queue, then a row.