Motion — Physics

mandatory

Motion physics for Koder UI — principles, performance budget, forbidden patterns, reduced-motion contract, and spring tokens (Material 3 Expressive). The "how it moves" half of Motion (the "how long" half lives in `easing-duration.kmd`).

Spec — Motion: Physics

Facet *isual*do Koder Design. Material parity: https://m3.material.io/styles/motion/overview/how-it-works.

Principles

  1. *otion has a reason*— state change, hierarchy, spatial cue.

    Decorative animation (idle particles, ambient parallax) prohibited in app UI; allowed in marketing landings only.

  2. *ast enough to disappear*— UI animations are sub-300ms.

    Above that, the user notices "the app is animating" instead of "the app responded."

  3. *asing matches intent*— entering uses decelerate (fast→slow,

    "arriving"); exiting uses accelerate (slow→fast, "leaving"); onscreen tweaks use easeinout. Detail: `easingduration.kmd`.

  4. *educed motion respected*— every animation collapses to

    instant (0ms) under prefers-reduced-motion: reduce.

R1 — Reduced motion contract

When prefers-reduced-motion: reduce:

  • All durations → 0ms (instant transitions)
  • Scale animations → opacity only (no scaling)
  • Continuous animations → static (loaders use indeterminate progress text instead of spinning)
  • Parallax → disabled
  • Auto-playing carousels → static, manual advance only
  • Spring animations → snap to end state (0ms); no overshoot, no oscillation

CSS implementation:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0ms !important;
    scroll-behavior: auto !important;
  }
}

R2 — Performance budget

  • All transitions composited via GPU (transform, opacity)
  • Avoid animating: width, height, top, left, padding, margin (causes

    layout reflow)

  • Singleframe check: animations stay at 60fps on midtier mobile
  • Coordinated multielement animations: stagger (50100ms offset) so

    the GPU doesn't paint all frames simultaneously

R3 — Forbidden patterns

  • ❌ Animations > 700ms (except marketing hero)
  • ❌ Bounce/elastic curves in UI (only marketing landings) — see R4 spring exception
  • ❌ Auto-playing animations on tab return (cumulative motion blur)
  • ❌ Animating colors via JS (use CSS transitions)
  • animation: ... infinite outside loaders / static decoration in landings

R4 — Spring tokens (Material 3 Expressive)

Durationbased tokens (`easingduration.kmd`) describe *ow long*an animation takes; spring tokens describe *ow it moves*— physics-driven (stiffness, damping ratio, mass) instead of time-driven. Spring physics is what unlocks Expressive: interruptible gestures, natural overshoot/ bounce on movement, smooth chaining of state changes mid-animation.

Two families, separated by what they animate:

R4.1 — Spatial tokens (movement, size, position)

Used for any property that has a *patial meaning*— translate, scale, layout size, rotation, container shape morph. May overshoot slightly (natural physics) — bounded so the overshoot reads as "settled" not "jiggle."

Token Stiffness Damping ratio Mass Use
motion-spatial-fast 1400 0.9 1.0 Small element movement (chip select, switch thumb)
motion-spatial-default 700 0.9 1.0 Standard UI translate (drawer, sheet, FAB position)
motion-spatial-slow 300 0.8 1.0 Hero element settle, container transform

R4.2 — Effect tokens (fade, color, opacity)

Used for properties that are *on-spatial*— opacity, color channel, blur radius, elevation alpha. High damping ratio (≥1.0) guarantees zero overshoot (a half-faded element bouncing back into visibility is wrong).

Token Stiffness Damping ratio Mass Use
motion-effect-fast 3800 1.0 1.0 Hover/press color tween, ripple fade
motion-effect-default 1600 1.0 1.0 Standard opacity/color crossfade
motion-effect-slow 800 1.0 1.0 Hero crossfade, scrim reveal

R4.3 — Decision tree: duration token vs spring token

Is the animation INTERRUPTIBLE mid-flight (drag, fling, gesture)?
├── YES → use spring (R4.1 for spatial, R4.2 for effect)
└── NO  → does it move position/size?
          ├── YES, and it has natural physics (FAB morph, sheet snap)
          │       → use spring (R4.1)
          └── NO, it's a deterministic timed transition (modal entry,
                  cross-fade, loader)
                  → use duration tokens (`easing-duration.kmd` R1-R2)

Spring tokens are *dditive*to duration tokens — duration tokens remain canonical for deterministic transitions. New code should prefer springs when the motion is gestural or spatial; legacy duration use stays valid.

R4.4 — Surface bindings

Surface API
Flutter SpringSimulation + SpringDescription(mass, stiffness, damping)
Compose spring(stiffness=, dampingRatio=) from androidx.compose.animation.core
SwiftUI .spring(response:, dampingFraction:, blendDuration:) (response = 2π√(mass/stiffness))
Web CSS linear() easing precomputed from spring; or JS lib (Motion One, Framer Motion)

For Web, generate the linear(0, 0.1, 0.3, 0.6, 0.85, 1.0) form from the spring params at build time per preset; do NOT compute springs in JS at runtime.

R4.5 — Per-preset variation

Preset Spring behavior
material3 Defaults (Expressive baseline)
material_expressive +20% stiffness on Spatial (snappier)
cyberpunk_neon -30% stiffness on Spatial (more "weighty" feel)
brutalist / terminal_classic Springs replaced by 0ms snap
glassmorphism Damping ratio +10% (gentler settle)
  • motion.kmd — index over all Motion sub-specs
  • motion/easing-duration.kmd — duration tokens + easing curves
  • motion/transitions.kmd — transition pattern catalog
  • interaction/states.kmd — state-level motion details
  • themes/ui-style.kmd — per-preset motion variation

Source: ../home/koder/dev/koder/meta/docs/stack/specs/themes/motion/physics.kmd