Motion — Physics
themes specs/themes/motion/physics.kmd
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`).
When this spec applies
Primary triggers
- Tune spring tokens (stiffness, damping, mass) for any Koder surface
All triggers
- Implement a spring-driven animation (gestural, interruptible)
- Add motion to an interactive Koder surface
- Audit an animation against the reduced-motion contract
Specification body
Spec — Motion: Physics
Facet Visual do Koder Design. Material parity: https://m3.material.io/styles/motion/overview/how-it-works.
Principles
- Motion has a reason — state change, hierarchy, spatial cue. Decorative animation (idle particles, ambient parallax) prohibited in app UI; allowed in marketing landings only.
- Fast enough to disappear — UI animations are sub-300ms. Above that, the user notices "the app is animating" instead of "the app responded."
- Easing matches intent — entering uses decelerate (fast→slow,
"arriving"); exiting uses accelerate (slow→fast, "leaving");
on-screen tweaks use ease-in-out. Detail:
easing-duration.kmd. - Reduced 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)
- Single-frame check: animations stay at 60fps on mid-tier mobile
- Coordinated multi-element animations: stagger (50-100ms 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: ... infiniteoutside loaders / static decoration in landings
R4 — Spring tokens (Material 3 Expressive)
Duration-based tokens (easing-duration.kmd) describe how long an
animation takes; spring tokens describe how 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 spatial 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 non-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 additive 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) |
Cross-link
motion.kmd— index over all Motion sub-specsmotion/easing-duration.kmd— duration tokens + easing curvesmotion/transitions.kmd— transition pattern cataloginteraction/states.kmd— state-level motion detailsthemes/ui-style.kmd— per-preset motion variation
References
specs/themes/motion.kmdspecs/themes/motion/easing-duration.kmdspecs/themes/motion/transitions.kmdspecs/interaction/states.kmd