I wanted my site’s header to feel physical. Not just sticky, but, magnetic. On scrolling down, the header pulls itself to the top edge of the viewport. Scrolling back up releases it as it drifts to its resting position. Like a watch snapping onto a magnetic charger!

The starting point

A sticky nav bar, floating 16px from the top, with a box-shadow and 4px rounding. I reached for Motion for the transition — 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 any motion. It just moved. There was 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.

This, however, was jittery. Two competing springs on the same element created a visual conflict—horizontal wobble layered on top of vertical movement.

Iteration 3: Squeeze removed

Backed out the scaleX animations. Just the softer position spring.

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, the scale was part of the same animate call as the 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. The damping needed to go up.

Iteration 5: The final version

stiffness: 380, damping: 28. Fast pull, immediate settle. The expand stays at scaleX: 1.012. The undock uses a different spring: stiffness: 200, damping: 30. This was softer, slower. Magnets snap on harder than they come off.

Final — stiffness: 380, damping: 28. Clean snap, no wobble, subtle expand.

No bounce. 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 the position. As a separate spring, it jittered. Unified, it became part of one gesture.