open-design/craft/animation-discipline.md
Mohamed Abdallah 5da21e4054
feat(craft): animation-discipline module + opt-ins on mobile-app, mobile-onboarding, gamified-app (#515)
* feat(craft): add animation-discipline + opt-ins on mobile-app, mobile-onboarding, gamified-app

Animation discipline is the second behavioral craft module proposed in
#501 and explicitly invited in @mrcfps's post-merge comment on #502.

Differentiation from prior art (LottieFiles motion-design-skill, MIT,
96 stars): citation-grounded against primary sources rather than
asserted. Anchors:

- Tversky/Morrison/Bétrancourt 2002 (IJHCS) on the one demonstrated
  win-condition for animation
- Heer & Robertson TVCG 2007 on staging (with the actual durations
  they tested, not the laundered '300-1000ms' rule)
- Harrison/Yeo/Hudson CHI 2010 on perceived-duration scope (progress
  bars only, not skeletons)
- Doherty & Thadani IBM 1982 productivity numbers
- Material 3 motion tokens (M3 standard vs M2 legacy delta)
- IBM @carbon/motion durations
- Apple SwiftUI Animation API published defaults
- W3C View Transitions API + WCAG 2.2.2/2.3.3 calibration
- WebKit 2017 prefers-reduced-motion rationale

The 'common mistakes (lint these)' section busts five specific
folklore claims that don't survive primary-source check, including
the Doherty-400ms attribution and the M2-vs-M3 standard easing
confusion.

Three skills opt in via od.craft.requires:
  - mobile-app (animation-heavy mobile screens)
  - mobile-onboarding (multi-screen flow with transitions)
  - gamified-app (animations central to the format)

Refs #501.

* fix(craft): address review findings on animation-discipline

Six findings from @lefarcen's CHANGES_REQUESTED review on #515,
addressed in one pass. Reviewed by codex across three loops before
push.

P1 integration gaps:
- gamified-app and mobile-onboarding skills now require both
  state-coverage and animation-discipline (both render stateful UI
  with motion).
- craft/README.md silent-fallback example reframed as a
  planned-but-not-yet-vendored placeholder rather than a hard-coded
  next-to-ship slug. Note added pointing skill authors who arrive from
  older guidance at animation-discipline as the equivalent of the
  earlier 'motion' placeholder.

P2 reasoning completeness:
- > 500 ms duration row reframed: 'Reserved for cross-screen, staged,
  or platform-native transitions (e.g. M3 long2-extraLong4, Heer &
  Robertson 2007's per-stage recommendation)'. Surrounding paragraph
  rewritten with an enumerated category — 'Non-navigation
  microinteractions: hover, press, toggle, validation, chip selection,
  row expansion' — rather than the vague 'routine' term.
- New 'Flashing limits' subsection added in the Reduced motion
  section. WCAG 2.3.1 (Level A) three-flashes-in-any-one-second-period
  rule with the area/brightness threshold qualifier; WCAG 2.3.2 (AAA)
  unconditional rule. Photosensitive epilepsy framing.
- New 'Repeated and ambient motion' section added. Five rules covering
  iteration cap, WCAG 2.2.2 pause control after 5s, cancel-on-route,
  one-shot reward animations, and spinner timeout cross-referencing
  state-coverage.md.

File length now 154 lines (was 130, 80-110 craft target). Trade is
citation density and the new sections demanded by the integration
context (gamified/onboarding skills with looping motion).

Refs #501, #515.
2026-05-05 18:32:30 +08:00

8.9 KiB
Raw Permalink Blame History

Animation discipline craft rules

Universal rules for when motion earns its place in a UI and what numbers constrain it. The active DESIGN.md decides brand-specific motion personality; this file decides whether motion should run at all and at what duration, easing, and accessibility floor.

Grounded in primary sources: Tversky/Morrison/Bétrancourt 2002 (IJHCS), Heer & Robertson TVCG 2007, Harrison/Yeo/Hudson CHI 2010, Doherty & Thadani IBM Systems Journal 1982, Chang & Ungar UIST 1993, Material 3 motion tokens, IBM @carbon/motion, Apple SwiftUI Animation API, W3C View Transitions, WCAG 2.2.2 + 2.3.3, WebKit's 2017 prefers-reduced-motion rationale.

When motion earns its place

Tversky/Morrison/Bétrancourt's 2002 meta-analysis (IJHCS 57, pp. 247-262) found that every study claiming animation aids comprehension had a broken control — the static version had less information, different procedures, or hidden interactivity. When equalised, animation does not beat static for teaching complex systems. The single use case the paper endorses is real-time spatial or temporal reorientation: page transitions, container morphs, viewpoint changes, progress indicators (p. 257).

A follow-on hazard: Palmiter & Elkerton found animation-trained users declined one week after training, while text-trained users improved (Tversky 2002, p. 255). Animation's apparent short-term parity hides worse retention.

So animate when the user is moving through space, time, or state — navigation, container expansion, progress feedback, gesture follow-through. Don't animate to teach, decorate, signal "premium", or fill silence.

Duration thresholds

The cross-design-system convergence is 150 ms — Material 3 short3, IBM Carbon moderate-01, Shopify Polaris 150, Tailwind default, SLDS duration-fast all land here. Use it as the default duration for state-confirmation feedback.

Duration Use
50100 ms Instant feedback (button press, toggle commit, hover)
150 ms Default for state-confirmation
200300 ms Entering UI (modals, sheets, dropdowns)
300500 ms Cross-screen transitions, container morphs
> 500 ms Reserved for cross-screen, staged, or platform-native transitions (e.g. M3 long2-extraLong4, Heer & Robertson 2007's per-stage recommendation).

Non-navigation microinteractions — hover, press, toggle, validation, chip selection, row expansion — should stay under 500 ms. Past that the user notices the motion as motion and waits on the UI rather than working through it. Two qualifications: frequent animations (a hover effect seen 50 times per session) need to stay ≤200 ms; mobile animations should run 2030% shorter than desktop equivalents because travel distances are shorter.

Curve vs spring

Use a curve for opacity, color, and any property that changes value between two known points. Use a spring for position, scale, rotation, and gesture-driven motion — anything that should feel physical.

Material 3 standard easing is cubic-bezier(0.2, 0, 0, 1) — front-loaded; the trailing zero makes the curve hit its target instantly and settle. M2 standard was the symmetric cubic-bezier(0.4, 0, 0.2, 1), preserved in M3 under the name legacy. Anyone shipping the M2 curve and calling it "M3" is on legacy tokens. M3 emphasized is a two-segment Bézier path, not a single cubic-bezier; single-cubic approximations silently lose the front-loaded character. CSS linear() (Chrome 113+) is the only way to replicate it on a single property.

Apple's published SwiftUI default spring is (response: 0.5, dampingFraction: 0.825, blendDuration: 0). The widely cited .snappy = 0.25 s, .smooth = 0.35 s numbers are wrong — Apple's docs assign all three presets a 0.5 s base, differing only in bounce (0 / 0.15 / 0.3).

Spring framework defaults disagree. motion.dev's physics-mode default is ζ ≈ 0.5 (bouncy). React Spring's default is ζ = 0.997 (critically damped). Same word "default", opposite feel — React Spring's wobbly is the actual feel-equivalent of motion.dev's default. Pick consciously.

Reduced motion

Every animation that translates, scales, rotates, or parallaxes must respect @media (prefers-reduced-motion: reduce). WebKit shipped this in 2017 to address vestibular triggers; the W3C MQ5 spec lets the UA or author strip motion entirely or substitute static imagery — the spec does not mandate which.

Working rule: strip motion-on-an-axis (translate, scale, rotate, parallax). Keep opacity/color crossfades as substitutes when a state change still needs to be conveyed. Be explicit — the View Transitions API does not apply prefers-reduced-motion automatically; the author must add a query override on the pseudo-elements or skip startViewTransition entirely.

WCAG calibration: 2.2.2 (Pause/Stop/Hide) is Level A — the legal floor under ADA Title II 2024 / EN 301 549 / EAA — but it names cognitive, attentional, and reading populations, not vestibular. Vestibular language lives in 2.3.3, which is AAA. Don't conflate the two. Building for vestibular users is a craft commitment beyond the legal floor, not a WCAG mandate.

Flashing limits. WCAG 2.3.1 (Level A) permits flashing only when there are no more than three flashes within any one-second period, or the flashing area stays below the general and red flash thresholds. WCAG 2.3.2 (AAA) forbids flashing more than three times within any one-second period, regardless of area or brightness. The protected concern is photosensitive epilepsy; the legal floor isn't negotiable. For gamified UI, onboarding celebrations, sparkles, confetti, level-up bursts, and shimmer: avoid rapid flashing unless tested against the thresholds, and prefer one-shot animations over loops.

Repeated and ambient motion

The rules above target one-shot transitions. Looping motion (skeleton shimmer, idle backgrounds, autoplay, reward bursts) has different constraints.

  • Cap iteration count: carousels at 3-5 cycles then pause; skeleton shimmer until content lands, never indefinitely.
  • WCAG 2.2.2 (Level A) requires a pause control for any motion running longer than 5 seconds — moving, blinking, or scrolling content, not only video.
  • Cancel ambient motion on route change.
  • Reward animations are one-shot. Confetti, sparkles, level-up bursts fire once and dismiss; no looping timer.
  • Spinners must not run indefinitely. Escalate to progress/cancel states and stop animation at 60 s, matching state-coverage.md.

Cross-platform handoff

Native conventions diverge.

  • iOS uses spring physics with perceptual (response, dampingFraction) parameters. Apple HIG documents principles, not numerical curves; the SwiftUI Animation API JSON is the source for actual numbers. UIView curve cubic-beziers commonly cited online are reverse-engineered, not Apple-published.
  • Android uses cubic-bezier curves through M3 motion tokens (501000 ms range, 16 named durations). Predictive back is a gesture-progress primitive, not a transition primitive — BackEvent.progress is sampled per-frame from the touch stream and the destination is rendered behind the current surface while still on it. Cancellation is a first-class lifecycle state.
  • Web has the View Transitions API (default 0.25 s, no easing specified by the spec — falls through to CSS ease). Same-document support 90.94%; cross-document 87.82%. Cross-document is same-origin and user-initiated only.

A "one curve fits all platforms" approach loses on each. If the brief specifies platform fidelity, follow the platform; if it specifies brand consistency, pick one motion vocabulary and apply it everywhere.

Common mistakes (lint these)

  • "Skeleton screens feel 11% faster" — Harrison/Yeo/Hudson CHI 2010 measured backwards-decelerating ribbed determinate progress bars (n=16). The induced-motion mechanism doesn't transfer to skeletons.
  • "Heer & Robertson recommend 3001000 ms eased transitions" — they tested 1.25 s and 2 s only. Their recommendation is "~1 second per stage".
  • "Doherty Threshold = 400 ms" — the 1982 paper does not contain "400". The lowest threshold actually measured is 300 ms.
  • M2 standard easing cubic-bezier(0.4, 0, 0.2, 1) labelled as "Material 3". M3's standard is cubic-bezier(0.2, 0, 0, 1).
  • Animations that perform a state change rather than confirming one that has already happened. Optimistic UI first; motion second.
  • More than 500 ms on any non-cross-screen transition.
  • Animation as the only signal of state change. Reduced-motion users miss it; always pair with a static affordance (color, position, label).
  • Ignoring prefers-reduced-motion on transform-based animations — the highest-cost vestibular triggers.
  • Curve-based animation on a transform: scale() that should feel physical. Use a spring.
  • Hero choreography in productivity tools. Motion budget belongs inside the product on functional micro-feedback, not on landing-page sequences.
  • Decorative motion in the working canvas of a productivity tool.