You Might Also Like
Magnetic Button — Free HTML CSS JS Hover Snippet
Magnetic Button · Buttons · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Magnetic Button — getBoundingClientRect Vector Math & distance-based translateX/Y

A magnetic button physically pulls toward the cursor as it hovers near — the button moves to meet the mouse, then snaps back when the mouse leaves. The effect makes CTAs feel tactile and interactive, turning a static button into a micro-interaction that communicates responsiveness — much like the click ripple. It is used on premium agency sites, creative portfolios, and any interface aiming for a distinctive, physical UI quality.
The vector math
The JS reads the button's position with getBoundingClientRect() to get cx (center x) and cy (center y). The mouse offset from center is dx = e.clientX - cx and dy = e.clientY - cy. The distance from the center is Math.sqrt(dx*dx + dy*dy).
A maximum effective distance is set to Math.max(rect.width, rect.height) * 1.2 — the button only reacts when the cursor is within 1.2× the button's larger dimension. A falloff factor scales with (1 - dist / maxDist) * strength — the closer the cursor, the stronger the pull. The final offset is (dx * factor / maxDist, dy * factor / maxDist) applied as translateX/Y.
The data-strength attribute
Each button has a data-strength attribute (defaulting to 30). Higher values make the button move more; lower values make it subtler. The two demo buttons use different strengths to show the range.
The snap-back
On mouseleave, btn.style.transform = 'translate(0,0)' resets the position. The CSS transition: transform 0.15s cubic-bezier(0.23,1,0.32,1) applies an elastic ease-out so the snap-back has a slight overshoot quality.
Adding to any existing button
Add data-strength="30" to any button — including a gradient button — add the three event listeners from the JS, and ensure the button has transition: transform 0.15s in its CSS. No other changes needed.
The vector math
On mousemove inside the magnetic zone, two calculations run: (1) distance = Math.hypot(dx, dy) where dx and dy are the cursor offset from the button centre. (2) If distance < strength radius, apply a force: x += (cursorX - buttonCentreX) * 0.3; y += (cursorY - buttonCentreY) * 0.3. This moves the button 30% of the distance toward the cursor. The button position is updated via transform: translate(x + 'px', ' + y + 'px'), and lerp smoothing (x += (targetX - x) * 0.12) eases the movement each requestAnimationFrame frame.
The spring return
On mouseleave, the target position resets to (0, 0). The lerp loop continues running until x and y are close enough to zero (abs < 0.5), then it stops. This creates the elastic return animation — the button springs back to its natural position with decreasing velocity, exactly like a physical magnet being released.
Configuring the attraction strength
Increase the 0.3 multiplier for stronger attraction (button moves more per pixel of cursor distance). Decrease for subtler effect. The strength radius (typically 80-120px) controls how close the cursor must be to trigger attraction. Smaller radius = more precise interaction; larger = more ambient magnetic feel.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not need to re-derive the falloff formula from scratch. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the factor calculation multiplies (1 - dist / maxDist) by strength before scaling dx and dy, and why maxDist is defined relative to the button's own width and height rather than a fixed pixel constant. The same assistant can help optimize it, for instance asking whether setting btn.style.transform directly on every mousemove event risks layout thrash compared to batching the write inside a requestAnimationFrame callback. It is also useful for extending the effect: ask it to add a lerped spring-back instead of the instant CSS-transition snap, make the button's inner label counter-rotate slightly for a parallax feel, or apply the same technique to an icon inside a card rather than a button. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "magnetic button" hover effect in plain HTML, CSS, and JavaScript with no libraries.
Requirements:
- One or more button elements, each carrying a data-strength attribute controlling how far that specific button can be pulled, defaulting to a sensible value if the attribute is missing or unparsable.
- On mousemove over a button, compute the button's center point using getBoundingClientRect (not a hardcoded position), then compute the cursor's offset from that center as a vector (dx, dy) and its straight-line distance using the Pythagorean theorem.
- Define an effective interaction radius relative to the button's own measured dimensions (e.g. the larger of its width or height times a multiplier), not a fixed pixel value, so bigger buttons naturally get a bigger magnetic zone.
- Only apply a transform when the cursor is within that effective radius; the pull strength must fall off smoothly from full strength at the center to near zero at the radius edge, using a formula proportional to (1 - distance / radius).
- Apply the computed pull as a CSS transform: translate on the button element (not on an inner wrapper), scaled by the per-button strength attribute, and give the button a CSS transition on the transform property so that both the pull and the reset below feel eased rather than instant.
- On mouseleave, reset the button's transform back to no translation, relying on the existing CSS transition for the snap-back rather than manually animating the reset in JavaScript.
- The label/text inside the button must not itself intercept pointer events in a way that breaks the parent button's mousemove tracking.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Hover near each buttonMove the cursor near the buttons in the preview without clicking. Each button pulls toward the cursor based on its data-strength value.
- 2Change the pull strengthIn the HTML panel, update the data-strength attribute on each button. Higher values (40-60) create stronger pull; lower values (10-20) are more subtle.
- 3Change button stylesUpdate the background gradient, border-radius, font-size, and padding in the CSS panel to match your design.
- 4Add to an existing buttonAdd data-strength="30" to your button, copy the JS mousemove and mouseleave listeners, and add transition: transform 0.15s to its CSS.
- 5Adjust the effective radiusIn the JS panel, change the 1.2 multiplier in maxDist to control how far the cursor must be before the button starts reacting.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The mousemove handler calculates the vector from the button centre to the cursor (dx, dy) using getBoundingClientRect. It computes the distance and a falloff factor that decreases from strength to 0 as the cursor moves from the centre to the edge of the effective radius. This factor scales dx and dy to produce the translateX/Y offset.
data-strength controls the maximum pixel offset the button moves. A value of 30 moves the button up to 30px toward the cursor at maximum proximity. Change it per button to create different pull intensities across your UI.
The effective radius is Math.max(rect.width, rect.height) * 1.2. Change 1.2 to a larger value like 2.0 to start attracting the cursor from further away, or to 0.8 to require the cursor to be nearly on the button before reacting.
Add data-strength="30" to your button HTML. Copy the mousemove and mouseleave event listeners from the JS panel. Add transition: transform 0.15s cubic-bezier(0.23,1,0.32,1) to your button CSS. No additional markup needed.
The CSS transition: transform 0.15s cubic-bezier(0.23,1,0.32,1) applies an elastic ease-out to all transform changes including the reset to translate(0,0) on mouseleave. The cubic-bezier values create a slight overshoot and bounce.
Yes. Click "JSX" to download a React component. In React, attach the mousemove and mouseleave handlers via onMouseMove and onMouseLeave props. Track the transform in useState or apply it directly to the element via useRef to avoid re-render overhead.