toolkit

Motion & animation audit · Playbook

Motion rules — with provenance

The rules oricorio enforces, each tied to a primary source and the date it was last checked against that source. RULES in scripts/oricorio.mjs is the machine-readable copy of this table — change both together.

Why provenance: an audit that can’t say where a threshold came from is folklore with a verdict attached. Every row here names a first-party source (W3C/WAI, web.dev, Apple) and a lastVerified date, so a stale rule is visible rather than silently authoritative.


motion-no-reduced-motion — serious

Fires when the page declares one or more animated rules and contains zero @media (prefers-reduced-motion: reduce) blocks.

Vestibular disorders, migraine and general motion sensitivity make animation actively painful for a real slice of users. The OS-level preference exists precisely so they can ask for less; a page that declares motion and never checks it gives them no way out.

Fix: add a prefers-reduced-motion: reduce block that keeps opacity and colour transitions and drops transform-based movement. Reduced motion means fewer and gentler, not zero — transitions that aid comprehension should survive.


motion-infinite — serious

Fires when a rule outside a reduced-motion block declares animation-iteration-count: infinite.

Motion that starts automatically, lasts more than five seconds, and runs alongside other content must be pausable. An infinite animation by definition never stops on its own.

Fix: give the user a pause control, or stop the animation under prefers-reduced-motion. Note that a spinner tied to a genuinely pending operation is usually defensible — this rule flags the pattern for a human decision rather than asserting a definite failure.


motion-layout-property — moderate

Fires when a transition or keyframe animates width, height, top, left, right, bottom, or any margin/padding — or uses transition: all, which sweeps them up by accident.

These properties force layout and paint on every frame, on the main thread. transform and opacity are handled by the compositor and skip both, which is the difference between a smooth animation and a janky one on a mid-range phone.

Fix: re-express the motion as transform (translate/scale) and opacity. Replace transition: all with an explicit property list.


motion-long-duration — minor

Fires when a non-looping animation or transition exceeds 500ms.

Long transitions make an interface feel sluggish, because the user ends up waiting on the animation rather than on the system. Perceived speed drops even when actual speed is unchanged.

Exempt: infinite/looping motion (spinners, marquees, progress). Constant motion is legitimately long-running, and flagging a 1s linear spinner was a false positive caught while building this skill.

Fix: bring one-shot UI motion under ~300ms. 500ms is the outer bound this check enforces, chosen to flag only the clear offenders rather than to police taste.


motion-indeterminate — incomplete

Fires when one or more stylesheets could not be read.

A cross-origin stylesheet throws SecurityError on .cssRules, so any motion it declares is invisible to this audit. Reporting PASS in that situation would be a false clean bill of health, so this finding forces the verdict to REVIEW.

Fix: serve the stylesheet same-origin, or add crossorigin plus Access-Control-Allow-Origin, to make the audit complete. Otherwise read the result as partial — it is a gap in our coverage, not a defect in the page.


Deliberately not checked

Rules that were considered and left out, so they don’t get re-proposed:

Not checked Why
Easing direction (ease-in on entrances) Real guidance, but it grades taste rather than a defect, and a static read can’t tell an entrance from an exit reliably.
scale(0) starts Same — a style opinion, not a failure.
Stagger delays Needs intent to judge; a long delay may be deliberate.
JS-driven motion (WAAPI, inline styles set at runtime) Not visible in the CSSOM at all. A genuine coverage limit, documented in SKILL.md rather than guessed at.