I wanted this site's header to feel physical—not just sticky, but magnetic. Scrolling down pulls it to the top edge of the viewport; scrolling back up releases it to drift home. Like a watch snapping onto a magnetic charger.

I reached for Motion and spring physics to get there. This is the iteration log: five iterations before the interaction finally felt right.


Role
Designer & Developer
Focus
Interaction Design, Micro-animation
Team
Me, myself, and I
Timeline
Feb 2026

Starting point

A sticky nav bar, floating 16px from the top, with a box-shadow and 4px rounding. Spring physics felt right for something magnetic—springs accelerate as they approach, then settle on contact.

Iteration 1: Stiff spring

stiffness: 400, damping: 25. The top offset animates from 16px to 0; border-radius flattens at the top when docked.

Iteration 1 — stiff spring (400/25), position and border-radius only.

The spring resolved so fast you couldn’t perceive the motion. No sense of pull.

Iteration 2: Softer spring, plus a squeeze

Loosened to stiffness: 300, damping: 18 for more visible overshoot. Added a horizontal squeeze: on dock, the header compresses to scaleX(0.985) and springs back; on undock, a slight exhale to scaleX(1.005). Two separate animate() calls—one for position, one for scale.

Iteration 2 — softer spring (300/18) with horizontal squeeze on dock/undock.

Jittery. Two competing springs on the same element—horizontal wobble layered on top of vertical movement.

Iteration 3: Squeeze removed

Backed out the scaleX animations. Softer position spring only.

Iteration 3 — softer spring (300/18), squeeze removed.

Cleaner, but the dock was missing something. The header moved to the right place without feeling like it arrived.

Iteration 4: Expand instead of squeeze

What if the header expanded on dock instead of compressing? scaleX: 1.012 when docked, back to 1 on undock. This time, scale was part of the same animate() call as position—one spring driving both properties.

Iteration 4 — subtle expand (scaleX: 1.012) on dock, shrink back on undock.

The expand gave the dock weight. But the spring was still loose: a visible bounce on landing that read as elastic, not magnetic. Damping needed to go up.

Iteration 5: Tighter damping

stiffness: 380, damping: 28. Faster pull, immediate settle. The expand stayed at scaleX: 1.012.

Iteration 5 — stiffness: 380, damping: 28. Clean snap, no wobble.

No bounce on landing. Undock still felt the same as dock, though—and magnets snap on harder than they come off.

Final

Asymmetric springs: undock loosens to stiffness: 190, damping: 29. Softer, slower release. Dock stayed at 380; damping came down from 28 to 25—still no bounce, just a little more pull on the way in.

Final — asymmetric dock/undock springs. Pull up, widen slightly, land.

The header pulls up, widens slightly, lands. The asymmetry between dock and undock seems to have worked.

What I took away

Damping controls character more than stiffness does. Stiffness is speed; damping is whether the spring overshoots, wobbles, or stops dead. For a magnetic interaction, both need to be high: fast and decisive.

The scaleX expand only worked when it was part of the same animate() call as position. As a separate spring, it jittered. Unified, it became part of one gesture.