Motion — Physics
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
- *otion has a reason*— state change, hierarchy, spatial cue.
Decorative animation (idle particles, ambient parallax) prohibited in app UI; allowed in marketing landings only.
- *ast enough to disappear*— UI animations are sub-300ms.
Above that, the user notices "the app is animating" instead of "the app responded."
- *asing matches intent*— entering uses decelerate (fast→slow,
"arriving"); exiting uses accelerate (slow→fast, "leaving"); on
screen tweaks use easeinout. Detail: `easingduration.kmd`. - *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)
- Single
frame check: animations stay at 60fps on midtier mobile - Coordinated multi
element animations: stagger (50100ms offset) sothe 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)
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) |
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