How to Implement Magnetic Micro-Physics with Motion useMotionValue in Taste-Skill

Use useMotionValue combined with useSpring and useTransform to drive cursor-following animations outside React's render cycle, strictly avoiding useState for continuous pointer tracking.

The Taste-Skill repository by Leonxlnx leverages Motion (the modern motion/react entry point for Framer Motion) as its exclusive animation engine for UI micro-interactions. When building magnetic effects—interface elements that subtly pull toward the cursor—the codebase mandates a specific architectural pattern centered on magnetic micro-physics with Motion useMotionValue to ensure GPU-accelerated performance without layout thrashing.

Why useMotionValue Beats useState for Magnetic Effects

According to skills/taste-skill/SKILL.md lines 357-363, the repository explicitly bans React useState for any continuously-changing input such as mouse position or pointer physics. The performance difference is stark:

Concern useState useMotionValue
Re-render on every frame Causes full React tree render Updates only the animated element
Mobile performance Poor (jank) Excellent (GPU-accelerated)
Cleanup / lifecycle Manual Automatic when component unmounts

Motion values live outside React's render cycle, writing directly to the DOM via refs rather than triggering state updates. This prevents layout thrashing and maintains 60fps even on constrained devices.

The Taste-Skill Architecture for Magnetic Micro-Physics

The canonical implementation appears in two key files:

  • skills/taste-skill/SKILL.md (lines 357-363): "Implement EXCLUSIVELY with Motion's useMotionValue / useTransform outside the React render cycle. Never useState."
  • skills/taste-skill-v1/SKILL.md (lines 69-71): Earlier version specifying the same requirement for buttons that pull toward the mouse cursor.

This architecture satisfies the repository's "Motion claimed = motion shown" rule for MOTION_INTENSITY > 4, ensuring that any advertised animation actually runs via Motion's optimized engine.

Motion Values Outside the Render Cycle

The critical distinction is that useMotionValue creates mutable state that Motion manages internally. When you call motionValue.set(newValue), Motion updates the DOM node directly without notifying React, bypassing the virtual DOM diffing entirely.

The "Never useState" Rule

The SKILL.md files explicitly warn against using useState for cursor tracking. Setting React state on every mousemove event forces a component re-render on every frame, destroying performance for micro-interactions.

Step-by-Step Implementation

Follow these architectural steps derived from the Taste-Skill source code:

1. Isolate the Client Component

Create a client-only leaf component to prevent server-side rendering conflicts with window-dependent logic.

'use client';

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

2. Instantiate Motion Values and Springs

Initialize raw motion values for cursor deltas, then pipe them through springs for physical smoothing.

const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);

const springX = useSpring(mouseX, { stiffness: 400, damping: 30 });
const springY = useSpring(mouseY, { stiffness: 400, damping: 30 });

3. Calculate Magnetic Offset with useTransform

Map the spring values to limited translation ranges to create the "magnetic" pull effect.

const translateX = useTransform(springX, [-100, 100], [-15, 15]);
const translateY = useTransform(springY, [-100, 100], [-15, 15]);

4. Attach Cursor Listeners (Direct Writes)

Update motion values directly in event handlers—no setState calls allowed.

const handleMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
  const rect = e.currentTarget.getBoundingClientRect();
  const offsetX = e.clientX - (rect.left + rect.width / 2);
  const offsetY = e.clientY - (rect.top + rect.height / 2);
  mouseX.set(offsetX);
  mouseY.set(offsetY);
}, [mouseX, mouseY]);

const handleMouseLeave = useCallback(() => {
  mouseX.set(0);
  mouseY.set(0);
}, [mouseX, mouseY]);

5. Respect Reduced Motion Preferences

Wrap logic with useReducedMotion() to provide static fallbacks for accessibility, as referenced in SKILL.md lines 372-384.

const reduce = useReducedMotion();

if (reduce) {
  return <button className="static-button">{children}</button>;
}

Complete Code Example

Here is the production-ready pattern implemented in skills/taste-skill/SKILL.md:

'use client';

import { motion, useMotionValue, useSpring, useTransform, useReducedMotion } from 'motion/react';
import { useCallback } from 'react';

export const MagneticButton = ({ children }: { children: React.ReactNode }) => {
  const reduce = useReducedMotion();

  // Motion values live outside React's render cycle
  const mouseX = useMotionValue(0);
  const mouseY = useMotionValue(0);

  // Spring physics for smooth easing
  const springX = useSpring(mouseX, { stiffness: 400, damping: 30 });
  const springY = useSpring(mouseY, { stiffness: 400, damping: 30 });

  // Map pointer delta to limited translate range (±15px)
  const translateX = useTransform(springX, [-100, 100], [-15, 15]);
  const translateY = useTransform(springY, [-100, 100], [-15, 15]);

  // Update motion values directly—no state updates
  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const offsetX = e.clientX - (rect.left + rect.width / 2);
    const offsetY = e.clientY - (rect.top + rect.height / 2);
    mouseX.set(offsetX);
    mouseY.set(offsetY);
  }, [mouseX, mouseY]);

  const handleMouseLeave = useCallback(() => {
    mouseX.set(0);
    mouseY.set(0);
  }, [mouseX, mouseY]);

  // Accessibility fallback
  if (reduce) {
    return <button className="static-button">{children}</button>;
  }

  return (
    <motion.button
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      style={{
        translateX,
        translateY,
        scale: useTransform(springX, [-100, 0, 100], [1.02, 1, 1.02]),
      }}
      className="magnetic-button"
    >
      {children}
    </motion.button>
  );
};

Key Source Files and References

  • skills/taste-skill/SKILL.md (lines 357-363): Defines the exclusive use of useMotionValue and the prohibition against useState for pointer physics.
  • skills/taste-skill-v1/SKILL.md (lines 69-71): Original specification for magnetic hover implementations on buttons.
  • CHANGELOG.md (lines 69-75): Documents the repository-wide migration to motion/react and the "Motion claimed = motion shown" principle.

Summary

  • Never use useState for cursor position or continuous physics; use useMotionValue to avoid React re-renders on every frame.
  • Compose with useSpring and useTransform to create smooth, physics-based magnetic pulls that map cursor distance to translation offsets.
  • Isolate animation logic in 'use client' components to prevent server-side rendering issues.
  • Respect accessibility by checking useReducedMotion() and providing static fallbacks.
  • Cleanup is automatic—useMotionValue handles unmounting, though manual DOM listeners should be cleaned in useEffect returns.

Frequently Asked Questions

Why does Taste-Skill ban useState for magnetic effects?

React useState triggers a full component re-render on every update. When tracking mouse movement, this creates 60 re-renders per second, causing layout thrashing and jank. useMotionValue updates the DOM directly without React's reconciliation cycle, maintaining GPU-accelerated 60fps performance as required by skills/taste-skill/SKILL.md lines 357-363.

How do I adjust the magnetic pull strength?

Modify the stiffness and damping parameters in useSpring. Higher stiffness (e.g., 500) creates a tighter, snappier pull toward the cursor, while lower damping (e.g., 15) increases the oscillation and "bouncy" feel. Map smaller input ranges in useTransform (e.g., [-50, 50] instead of [-100, 100]) to limit the maximum translation distance.

Does this pattern work with server components in Next.js?

No—motion values require browser APIs like window and document. You must mark components using useMotionValue with 'use client' at the top of the file. The Taste-Skill architecture specifically mandates isolating these as client-only leaf components to keep the server bundle free of animation logic.

How do I handle reduced motion preferences correctly?

Import useReducedMotion from motion/react and call it at the top of your component. If it returns true, return a static element without motion values. This pattern appears in skills/taste-skill/SKILL.md lines 372-384 and ensures compliance with accessibility standards while keeping the code path simple for users who prefer reduced motion.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →