Data attributes
The full markup contract: every data-cursor value, which variants respond to it, and the modifier attributes.
The cursor reacts to your markup, not to your component tree. Put a data-cursor attribute on
an element and any mounted variant that understands that value will respond when the pointer
enters it.
<button data-cursor="magnetic">Hire us</button>Attributes are the public API on purpose. They work in MDX, in CMS content, in a server component, and in a template rendered by something that is not React at all — no import, no wrapper, no ref.
Values
| Value | Effect | Responds |
|---|---|---|
grow | Scales the follower up over the element. | dot-ring, magnetic, spotlight, blend |
magnetic | Ring morphs onto the element's box and pulls the element toward the pointer. | magnetic |
blend | Scales the blend blob up. | blend |
hide | Fades the follower out entirely. | all six |
text | Reserved for a label-carrying variant. Currently a no-op. | none yet |
A variant that does not understand a value ignores it, so marking an element magnetic is
safe on a page running trail-dots — nothing happens, nothing errors.
Modifiers
These refine the behaviour of the element they sit on. All are optional.
| Attribute | Type | Meaning |
|---|---|---|
data-cursor-strength | 0–1 | How hard magnetic pulls this element. Defaults to the variant's strength prop. |
data-cursor-color | any CSS colour | Overrides the layer colour while this element is hovered. |
data-cursor-text | string | Label for the reserved text behaviour. |
data-cursor-strength is clamped to 0–1 when written through useCursorTarget. Out-of-range
values in raw markup are clamped by the variant instead, so nothing flies off screen either
way.
The typed helper
Raw attributes are stringly-typed, and data-cursor-stength is a typo TypeScript will never
catch. useCursorTarget exists for that:
import { useCursorTarget } from "@/components/pointerui/provider";
function Hire() {
return <button {...useCursorTarget({ variant: "magnetic", strength: 0.4 })}>Hire us</button>;
}It returns the attribute object and nothing else. There is no runtime behaviour attached to it, so it is a convenience rather than a requirement — reach for it in code you compile and skip it in content you author.
How targets are found
One pointerover listener on the document runs event.target.closest('[data-cursor]'). Two
things follow from that, and both are useful:
Nesting works. The nearest ancestor with the attribute wins, so wrapping a card in
data-cursor="grow" covers everything inside it without marking each child.
Dynamic content works. Nothing is bound at mount, so an element that appears later — a modal, a lazy list, a client-side route — is a target the moment it exists. There are no per-element listeners to attach and none to leak.
Scoping who reacts
If you need one region of the page to behave differently, mount a second provider with a
scope. Inside that element its engine owns the pointer; outside, the global one does. See
Engine for the details — this is the same mechanism every preview on this site
uses.