# How to Implement Magnetic Button Hover Physics with the Motion Library

> Implement magnetic button hover physics with the Motion library. Learn to use useMotionValue, useSpring, and useTransform for smooth, responsive button interactions.

- Repository: [Leon Lin/taste-skill](https://github.com/Leonxlnx/taste-skill)
- Tags: how-to-guide
- Published: 2026-06-05

---

**To implement magnetic button hover physics with the Motion library, capture cursor coordinates in `useMotionValue` instances, derive spring-smoothed offsets with `useSpring`, clamp the resulting travel to a safe range via `useTransform`, and bind the final translations to a `motion.button` element while respecting the user's reduced-motion preference.**

The `Leonxlnx/taste-skill` repository defines magnetic hover effects as a high-intensity motion pattern that must be built exclusively with Motion (the re-branded Framer Motion) and never with React `useState`. According to the design specification in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md), any motion intensity above level five requires spring-based magnetic micro-physics for interactive buttons. The implementation below follows every rule in that specification, including the mandatory `useReducedMotion` guard for accessibility.

## Motion Library Rules for Magnetic Micro-Physics

The taste-skill design system encodes these requirements in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md). Lines 132 and 138 establish the library choice and the ban on `useState` for continuous data, while lines 138–139 trigger magnetic micro-physics whenever `MOTION_INTENSITY` exceeds five.

- **Use Motion for animation.** The canonical import is `import { motion } from "motion/react"`, as established at line 132 of the repository's skill definition.
- **Never track continuous values with `useState`.** Cursor position, magnetic pull, and other high-frequency inputs must be handled with Motion's `useMotionValue`, `useSpring`, and `useTransform` utilities.
- **Enable magnetic micro-physics only when `MOTION_INTENSITY > 5`.** Buttons are expected to pull toward the cursor using real spring physics once intensity crosses this threshold.
- **Respect `prefers-reduced-motion`.** Effects above `MOTION_INTENSITY > 3` must collapse to a static state when the user prefers reduced motion, as documented at lines 526–529.

## Step-by-Step Implementation

### Create a Client-Only Component

Motion values live exclusively on the client, so the component must start with a `"use client"` directive. In [`src/components/MagneticButton.tsx`](https://github.com/Leonxlnx/taste-skill/blob/main/src/components/MagneticButton.tsx), place the directive at the very top before any imports to guarantee browser-side execution.

### Capture Cursor Position with `useMotionValue`

Read the mouse position inside an `onMouseMove` handler and write the values directly to `useMotionValue` instances. This bypasses React's render cycle entirely, satisfying the rule at line 138 that continuous inputs must never be stored in `useState`.

The handler should calculate the vector from the button's center to the cursor. Dividing that vector by a factor such as `8` slows the pull and creates a realistic magnetic field radius without overshooting.

### Apply Spring Physics and Clamp Translation

Pipe the raw offsets through `useSpring` to add natural elasticity. The Taste‑Skill specification favors smooth, real-world motion, so a configuration such as `stiffness: 150` and `damping: 25` gives the button an elastic but controlled feel.

After the spring, add a second transform with `useTransform` to clamp the travel distance. Limiting movement to a maximum of `±20 px` prevents layout shifts and keeps the button fully visible inside its container.

### Guard Against Reduced Motion

Always call `useReducedMotion()` at the top of the component. When this hook returns `true`, skip all calculations in the mouse handler and omit the `x`/`y` transforms from the `style` prop. The button remains static, complying with the accessibility policy defined at lines 526–529 of [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md).

## Production-Ready MagneticButton Component

The following file, [`src/components/MagneticButton.tsx`](https://github.com/Leonxlnx/taste-skill/blob/main/src/components/MagneticButton.tsx), demonstrates the complete pattern. It imports `motion`, `useMotionValue`, `useSpring`, `useTransform`, and `useReducedMotion` from `motion/react`, calculates center-relative offsets on `onMouseMove`, and resets the values to zero on `onMouseLeave`.

```tsx
// src/components/MagneticButton.tsx
"use client";

import { MouseEvent } from "react";
import {
  motion,
  useMotionValue,
  useSpring,
  useTransform,
  useReducedMotion,
} from "motion/react";

/**
 * A button that magnetically follows the cursor.
 * Works only when MOTION_INTENSITY > 5 and reduced‑motion is not requested.
 */
export function MagneticButton({
  children,
  className = "",
}: {
  children: React.ReactNode;
  className?: string;
}) {
  const reduce = useReducedMotion();               // ① Respect reduced‑motion
  const mouseX = useMotionValue(0);                // ② Store raw cursor X
  const mouseY = useMotionValue(0);                // ③ Store raw cursor Y

  // Spring‑y values that we will actually use for the translation.
  const springX = useSpring(mouseX, { stiffness: 150, damping: 25 });
  const springY = useSpring(mouseY, { stiffness: 150, damping: 25 });

  // Convert the raw spring values to a limited offset (max ±20px).
  const translateX = useTransform(springX, (v) => Math.max(-20, Math.min(20, v)));
  const translateY = useTransform(springY, (v) => Math.max(-20, Math.min(20, v)));

  // Mouse‑move handler – compute the vector from the button centre to the cursor.
  const handleMouseMove = (e: MouseEvent<HTMLButtonElement>) => {
    if (reduce) return; // skip calculations when reduced‑motion is on
    const rect = e.currentTarget.getBoundingClientRect();
    const offsetX = (e.clientX - (rect.left + rect.width / 2)) / 8; // ÷8 = slower pull
    const offsetY = (e.clientY - (rect.top + rect.height / 2)) / 8;
    mouseX.set(offsetX);
    mouseY.set(offsetY);
  };

  // Reset to zero when the cursor leaves the button.
  const handleMouseLeave = () => {
    mouseX.set(0);
    mouseY.set(0);
  };

  return (
    <motion.button
      className={`px-6 py-3 rounded-md bg-primary text-white ${className}`}
      style={reduce ? undefined : { x: translateX, y: translateY }} // ④ Apply offset
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
    >
      {children}
    </motion.button>
  );
}

```

### How the Component Satisfies the Guidelines

- **`use client` + Motion imports** guarantee client-side execution as required by the architecture.
- **`useMotionValue` and `useSpring`** capture continuous cursor data without triggering React re-renders, obeying the strict rule against `useState` for high-frequency values.
- **`useReducedMotion` guard** collapses the effect to a static button whenever the user prefers reduced motion.
- **Spring configuration (`stiffness: 150`, `damping: 25`)** delivers the real-world magnetic feel mandated by the high-intensity motion spec.
- **Limited translation (`±20 px`)** preserves layout stability by preventing the button from drifting beyond its container.

## Summary

- The `Leonxlnx/taste-skill` repository requires magnetic button hover physics to be implemented exclusively with the Motion library and never with React `useState`.
- Store cursor coordinates in `useMotionValue` instances, smooth them with `useSpring`, and clamp the result with `useTransform` before applying `x` and `y` translations.
- Always wrap the component in `"use client"` because Motion values run only in the browser.
- Call `useReducedMotion()` to disable the effect statically for users who prefer reduced motion, as enforced by [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md).

## Frequently Asked Questions

### Why can't I use `useState` for the mouse position?

[`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) at line 138 explicitly forbids tracking continuous values such as mouse coordinates with `useState` because every update would force a React re-render and destroy performance. Instead, `useMotionValue` writes values synchronously outside of React's render phase, and `useSpring` derives smooth animations without a single state commit.

### What is the correct import path for Motion hooks?

According to the taste-skill specification at line 132, the canonical import is `import { motion, useMotionValue, useSpring, useTransform, useReducedMotion } from "motion/react"`. This is the re-branded Framer Motion API used throughout the repository.

### How do I limit how far the button travels?

Pass the spring output through `useTransform` to clamp the offset. In the example above, `useTransform(springX, (v) => Math.max(-20, Math.min(20, v)))` caps horizontal movement at `±20 px`. You can adjust the `20` value to shrink or enlarge the magnetic field radius.

### When should I disable the magnetic effect?

Disable the effect whenever `useReducedMotion()` returns `true`, which happens when the user's system preferences request reduced motion. The repository's accessibility rule at lines 526–529 of [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) states that any intensity above level three must fall back to a static state in this scenario.