Skip to content
Menu

Experiment #001

Magnetic Button

The button tracks the cursor inside its hover zone and translates toward it with a damped offset, then springs back to rest when the pointer leaves. Pure Alpine.js and a CSS transform transition — no library, no rAF loop. Motion is disabled for users who prefer reduced motion.

Scene

Technical details

Experiment
FX-001
Category
Buttons
Stack
Tailwind + Alpine.js
Difficulty
Beginner
Dependencies
none
Weight
HTML 1.0 KB
Added
2026-08-20
Updated
2026-08-20

Lab notes

Tune the strength factor (0.2–0.5) to taste. Keep the hover zone padding generous: the magnetism reads better when the pull starts before the cursor reaches the button.

Take the code

HTML — code.html
<!-- Magnetic Button — Tailwind + Alpine.js -->
<div
    x-data="{
        dx: 0,
        dy: 0,
        move(event) {
            const rect = this.$refs.btn.getBoundingClientRect()
            this.dx = (event.clientX - rect.left - rect.width / 2) * 0.35
            this.dy = (event.clientY - rect.top - rect.height / 2) * 0.35
        },
        reset() {
            this.dx = 0
            this.dy = 0
        },
    }"
    @mousemove="move"
    @mouseleave="reset"
    class="inline-flex p-10"
>
    <button
        type="button"
        x-ref="btn"
        :style="`transform: translate(${dx}px, ${dy}px)`"
        class="rounded-full bg-zinc-900 px-7 py-3 text-sm font-medium text-white
               transition-transform duration-200 ease-out will-change-transform
               hover:bg-zinc-700 focus-visible:outline-2 focus-visible:outline-offset-4
               focus-visible:outline-zinc-500 motion-reduce:transition-none motion-reduce:transform-none!"
    >
        Hover me
    </button>
</div>

Adjacent

Specimens nearby

All Buttons →
FX-004 Buttons
FX-003 Buttons