Physics-based motion
Why springs feel better than cubic-bezier curves
A CSS transition follows a predetermined path. A spring responds to forces: target position, damping resistance, inertia.
Retarget a spring mid-flight and the velocity carries over. A CSS transition interrupted mid-flight either jumps or snaps.
The three parameters
Stiffness controls speed. 100 = lazy, 500 = snappy, 700+ = hard snap.
Damping controls resistance. 15 = visible bounce, 40 = smooth settle. Low = playful, high = professional.
Mass controls weight. 0.5 = zippy, 2 = heavy. Stay between 0.5 and 1.5.
Useful presets
| Preset | Stiffness | Damping | Use case |
|---|---|---|---|
| Snappy | 500 | 40 | General UI, no bounce, fast settle |
| Bouncy | 300 | 20 | Playful elements, notification badges |
| Gentle | 200 | 30 | Page transitions, large panels |
| Stiff | 700 | 50 | Small precise movements, toggles |
In product UI, bounce above 0.3 reads as cartoonish.
The Apple-style parameterisation
Apple uses duration and bounce instead of stiffness, damping, and mass.
bounce: 0: no overshoot (critically damped)bounce: 0.15: barely perceptible, naturalbounce: 0.3: visible overshoot, playful but restrained
Motion supports both.
SwiftUI's .spring(response: 0.35, dampingFraction: 0.85) maps to Motion's { type: "spring", duration: 0.35, bounce: 0.15 }.
Interruptibility
Every drag, gesture, or interruptible element should use springs.
Velocity inheritance
When a user flicks a card and a snap point catches it, the velocity should transfer to the spring.
Conceptual weight
Mass is a design decision about perceived importance. A 0.5 mass tooltip zips into view. A 1.5 mass modal settles deliberately. Destructive actions get critically damped springs (no bounce). Playful moments get underdamped springs (visible overshoot).
When not to use springs
- Opacity fades. Springs overshoot past 1.0. Use linear or ease-out.
- Colour transitions. Overshoot produces values outside the palette.
- Progress bars. Bouncing at 100% looks dishonest.
- Precise timing. Audio/video sync needs duration-based curves.