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'suseMotionValue/useTransformoutside the React render cycle. NeveruseState."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 ofuseMotionValueand the prohibition againstuseStatefor 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 tomotion/reactand the "Motion claimed = motion shown" principle.
Summary
- Never use
useStatefor cursor position or continuous physics; useuseMotionValueto avoid React re-renders on every frame. - Compose with
useSpringanduseTransformto 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—
useMotionValuehandles unmounting, though manual DOM listeners should be cleaned inuseEffectreturns.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →