← All components

Magnetic

A ring that snaps onto a target's bounding box and pulls the target itself toward the pointer. The morph is scaleX/scaleY on an SVG rect, never width and height — that choice was settled by measurement in Phase 0.

Live props — bound to the real component

36
14
1.5
12
0.35
0.25
0.35

Install

npx shadcn@latest add @pointerui/magnetic

Usage

app/page.tsx
import { CursorProvider } from "@/components/pointerui/provider";
import { Magnetic } from "@/components/pointerui/magnetic";

export default function Page() {
  return (
    <CursorProvider>
      <Magnetic />

      <button data-cursor="magnetic">Hover me</button>
    </CursorProvider>
  );
}

Props

Generated from magnetic/types.ts and the component's own defaults, so this table cannot drift from the code you are about to copy.

PropTypeDefaultDescription
sizenumber36

Resting ring diameter in px, before it morphs to anything.

paddingnumber14

Extra space between the ring and the target's bounds, per side.

ringWidthnumber1.5

Ring stroke width in px. Stays this width at any scale.

ringAlphanumber0.55

Ring stroke alpha, 0–1.

colorstring"var(--cursor-color, currentColor)"

Any CSS colour. Defaults to the --cursor-color custom property, which falls back to currentColor — never hardcode a hex.

radiusnumber12

Corner radius in px once morphed onto a target.

strengthnumber0.35

How hard a target is pulled toward the pointer, 0–1. Overridden per element by data-cursor-strength.

maxPullnumber0.25

Pull ceiling as a fraction of the target's own size. Without a clamp, a small target near the pointer flies away from its own layout.

lagnumber0.35

Follow duration in seconds while tracking the pointer or a target centre.

easestring"power3"

GSAP ease for the follow tween. Any ease string GSAP understands works — "power3", "expo.out", "circ", "none" for a linear follow.

This is the shape of the lag, where lag is its length. Changing it changes the character of the cursor more than any other prop.

Data attributes

Mark any element with one of these and this variant responds when the pointer enters it. Values it does not understand are ignored.

data-cursor="magnetic"
Ring morphs onto the element's box and the element is pulled toward the pointer.
data-cursor="grow"
Scales the resting ring without morphing.
data-cursor="hide"
Fades the ring out.

Notes & caveats

  • Pull is clamped to a fraction of the target's own size. Without a ceiling, a small target near the pointer flies out of its own layout.
  • Cached target rects are invalidated on scroll and resize, and by a ResizeObserver in border-box mode — content-box misses padding-only changes.
  • The stroke stays 1.5px at any scale because the scale is applied inside the SVG, where vector-effect: non-scaling-stroke works. Transform the wrapper instead and the stroke inflates with it.