Magnetic Header
Feb 2026 • Experiment
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.
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.
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.
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.
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.
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.
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.
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.