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