Magnetic Button
Overview
Inside its field the button is pulled toward the pointer, the label moving a little further than the button, and springs back when you leave.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds Magnetic Button to your project in your colors.
Where to use it
Call-to-action buttons for sign-ups, checkout, form submission and landing pages, whenever you need a primary action people want to click.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
$ npm i -g @8pt-dev/cli && 8pt add magnetic-button
mcp › Use 8pt MCP to call get_component("magnetic-button")
Code
Source for Magnetic Button. Motion uses only transform and opacity and respects the Reduce motion setting.
<div class="x-mag"> <button class="x-mag__btn" type="button"><span>Drag closer</span></button> </div>
.x-mag{display:grid;place-items:center;width:100%;height:100%;min-height:200px}
.x-mag__btn{width:132px;height:132px;border-radius:50%;border:1px solid var(--d-line,rgba(255,255,255,.14));background:var(--d-card,#101016);color:var(--d-fg,#fff);font:600 14px/1 var(--d-font,system-ui,sans-serif);cursor:pointer;will-change:transform;box-shadow:0 30px 60px -30px var(--d-accent,#4D74FF)}
.x-mag__btn span{display:inline-block;will-change:transform}const init = (root) => {
const zone = root.querySelector('.x-mag'), btn = zone.querySelector('button'), label = btn.querySelector('span');
let tx = 0, ty = 0, x = 0, y = 0, raf = 0;
const tick = () => {
x += (tx - x) * 0.16; y += (ty - y) * 0.16;
btn.style.transform = `translate(${x}px, ${y}px)`; label.style.transform = `translate(${x * 0.4}px, ${y * 0.4}px)`;
raf = Math.abs(tx - x) + Math.abs(ty - y) > 0.1 ? requestAnimationFrame(tick) : 0;
};
const kick = () => { if (!raf) raf = requestAnimationFrame(tick); };
zone.addEventListener('pointermove', e => {
const r = btn.getBoundingClientRect(); tx = (e.clientX - r.left - r.width / 2) * 0.35; ty = (e.clientY - r.top - r.height / 2) * 0.35; kick();
});
…Subscribe to Builder to see the full source.See pricing