opsinjs
HealthColour, status and attention

Motion in health UI

Motion may confirm an action, orient a reader and soften a transition. It may never carry urgency, meaning or a value, because a reader can miss it entirely.

What this means

Motion is the only visual channel in a health interface whose signal can be gone by the time a reader looks at the screen. It is also inexpensive to reach for: a pulsing red dot is a handful of CSS declarations, it reads as urgent to a design review watching a running screen, and it survives into the marketing screenshot. Those three properties carry the pattern through review, and none of them is evidence that the pulse reached a reader.

A pulse used to signal status behaves differently under five conditions, none of which a design review on a developer's own machine reproduces by default.

Channel or conditionWhat reaches the readerDetectability in a design review
Setting. prefers-reduced-motion: reduce is enabled at operating-system level.The element renders with no state change at all, so no status signal is present.Only where the reviewer's own device has the setting enabled.
Assistive technology. The screen is read aloud by a screen reader.Motion is not exposed in the accessibility tree, so nothing is announced.Only where the reviewer runs a screen reader against the screen.
Capture. The reader screenshots the result and sends the picture to someone else.A still image carries no pulse, and the recipient sees an ordinary element.No, because the capture is read outside the product.
Arrival time. The reader reaches the screen after the animation has played.A finished animation conveys nothing to a reader who was not watching it.Rarely, because a reviewer opening the screen sees the animation from its start.
Physiology. The reader has a vestibular disorder, or anxiety that movement aggravates.The motion arrives and is distressing rather than informative.No, because the reviewer cannot observe another reader's response.

Those five are settings, assistive technologies, reader behaviours and populations rather than edge cases, and they overlap with the audience a health product serves. opsinjs has not measured what share of its readers sits in one of them, and the rules below do not depend on the share, because the status signal is carried in colour, icon and word regardless of whether any animation plays. See Clinical status semantics for that channel split.

In a health product, motion carries a second constraint, and that constraint concerns framing rather than availability. An animated number counting up to a result is a small flourish when the number is a step count. When the number is a lab value the reader has been waiting on, the count-up invites them to anticipate, and anticipation is the wrong state to put someone in when the content may be bad news.

The rule

Motion may support a message. It may never be the message.

Seven requirements follow from that rule, and the Basis column names the authority each one rests on.

#RequirementBasis
1Urgency is never conveyed by motion. Nothing pulses, flashes, shakes or bounces to indicate status.Status is colour, icon and word. See Clinical status semantics.
2A value is never revealed by animation. Health numbers appear at their final value, with no count-up, no dial sweep on first paint and no progressive reveal of a result.Opinion. See "Why (evidence)" below.
3Nothing that carries clinical meaning may auto-dismiss. A message that times out is a message the reader may never have read.The reader's presence at the moment of display cannot be assumed.
4Nothing flashes.Above three flashes per second, a seizure risk, prohibited outright. Below that threshold, rules 1 and 3.
5Every animation has a defined reduced-motion behaviour, stated per token. The fallback is an instant, complete and equally informative state rather than the absence of animation.See Reduced motion.
6Motion never delays access to a result. A reader who waits 400 ms on an entrance transition before reading their result is waiting too long.400 ms is illustrative rather than measured.
7Transitions preserve continuity. A surface morphs rather than being swapped, so a reader who looks away and back does not lose their place.See Choreography.

Motion has four legitimate jobs in this system: confirming that an input was received, showing where a surface came from and where it went, keeping a scroll position comprehensible, and making a loading state feel bounded. All four are orientation jobs rather than meaning jobs.

A surface arriving is an orientation job, not a meaning job
148

--opsin-ease-spring-sheet · --opsin-duration-spring-sheet. Under prefers-reduced-motion: reduce this becomes 1ms with a flat curve. The element still moves, and it simply arrives at once. The checkbox simulates the preference; if you have actually set it, app/globals.css has already applied it and the two states will look the same.

Why (evidence)

Applying it

Do

Render an urgent banner at its final position and appearance, with an assertive announcement to assistive technology. Its weight comes from contrast, size, position and words.

Don’t

Pulse the banner border to make it feel more urgent. Under prefers-reduced-motion: reduce the border renders no state change at all, so the pulse contributes nothing to those readers and carries a vestibular risk wherever motion is not suppressed.

Do

Show a result at its value the moment the screen paints. If the data is not there yet, show a Skeleton that reserves the exact layout and never implies a number.

Don’t

Animate 0 → 142 over 800 ms. For 800 ms the interface displays values that are not this person's result, one of which may be a value the reader has been dreading.

Do

Under reduced motion, show the sheet in place with a crossfade of opacity only, retaining every affordance. The reader gets the same information in the same structure.

Don’t

Under reduced motion, remove the transition and let a modal appear with no change of context at all. The reduced-motion path still has to show where the surface came from, because reduced motion suppresses movement rather than orientation.

Do

Use a skeleton or a progress indicator whose motion is bounded and non-urgent, and stop it when loading ends.

Don’t

Leave an indeterminate spinner on a screen indefinitely when a request has failed. An indeterminate spinner is rendered identically whether or not a request is outstanding, so after a failure it continues to assert that work is in progress.

Components that implement this

The motion tokens are spring-derived linear() easings, and each token declares its own reduced-motion behaviour rather than deferring to one global kill switch. Rule 5 therefore applies to the token definition, so a reviewer checks the token rather than whichever component happens to use it.

What this does not cover

  • How the easings are built. Spring parameters, the linear() generator and the token set are Motion.
  • Writing transitions in code. The code-level guidance is Motion in practice.
  • Sound and haptics. These are a separate channel with a separate budget and are covered in Sound and haptics.
  • Data visualisation transitions. Animating between two chart states has its own honesty problems and belongs to Data visualisation.
  • Games, rewards and streak animation. These sit outside the clinical surface and are the product's own decision, provided they do not use the visual channel that carries clinical status.

Updates to this page

Last read through against the system on 2026-09-02. Due for review every 12 months; expiry is reported by pnpm run check:freshness.

On this page