002INTERACTIONSPreview

Magnetic Button

A restrained interaction experiment exploring attraction, release, and the feeling of weight in a small interface object.

Difficulty
Beginner
Build time
60–90 minutes
Published
Jul 27, 2026
Updated
Jul 27, 2026

Interactive preview shell

Preview

Move toward the button and feel the attraction.

01 / Overview

A small button with a sense of weight.

Magnetic Button uses pointer distance to pull a CTA toward the user without making the interaction feel noisy or difficult to control.

02 / What you will learn

The idea behind the interaction.

  • Read pointer position relative to a button’s bounds.
  • Translate a normalized offset into a restrained movement range.
  • Reset the interaction cleanly when the pointer leaves.

03 / How it works

Structure first. Tuning second.

The button owns its offset. Pointer coordinates are normalized to the center of the element, scaled to a small range, and reset on leave.

04 / Build sequence

Step by step

01

Measure the button

Use the element bounds so the interaction works at any responsive size.
02

Normalize the pull

Convert pointer distance into a small, predictable translation.
03

Release with intent

Return to the resting position when the pointer leaves the target.
tsx
import { useState } from "react";

export function MagneticButton() {
  const [offset, setOffset] = useState({ x: 0, y: 0 });

  function trackPointer(event: React.PointerEvent<HTMLButtonElement>) {
    const bounds = event.currentTarget.getBoundingClientRect();
    setOffset({
      x: ((event.clientX - bounds.left) / bounds.width - 0.5) * 24,
      y: ((event.clientY - bounds.top) / bounds.height - 0.5) * 24,
    });
  }

  return (
    <button
      onPointerMove={trackPointer}
      onPointerLeave={() => setOffset({ x: 0, y: 0 })}
      style={{ transform: `translate3d(${offset.x}px, ${offset.y}px, 0)` }}
    >
      Start a conversation
    </button>
  );
}

05 / Notes

Production details belong in the experiment.

06 / Props & API

A stable surface for the future build.

reducedMotion
boolean
Keeps the preview readable without nonessential movement.
className
string
Allows the experiment shell to inherit the MADLAB layout.
status
MadlabStatus
Communicates whether the experiment is draft, preview, or published.

07 / Performance

Measure before adding complexity.

Keep animation work local to the interaction, avoid unnecessary state updates, and pause nonessential work when the preview is not visible.

08 / Accessibility

The visual idea still needs a clear interface.

Every future experiment should have a semantic name, keyboard path, reduced-motion fallback, usable contrast, and a text explanation that does not depend on the animation.

09 / Mobile

A considered small screen composition.

On narrow screens, the preview should simplify its visual workload, preserve the primary idea, and never require horizontal scrolling to understand the experiment.

Source actions

Final source actions

Source is available in the implementation block above.

jump to code