Halo Cursor

ProInteractivityWebGLpowered by @vectorvesper/motion

A GPU cursor trail shipping four presets — aurora, neon, ripple and sparks — alongside a generic component, a React hook, and an imperative mount function for non-React use. Colour, size and trail length are configurable per preset.

Halo Cursor

Included with membership

Members install Halo Cursor with one command and get the full source, the usage code and the Framer remix, licensed for client work. One membership covers the whole library and the CLI.

When to use it

A site-wide signature — mount it once near the root and it follows the pointer everywhere. Four presets ship with it, and the preset is the decision that matters; the props only tune it. Because it replaces something the operating system already draws, treat it as a commitment for the whole site rather than one page.

  • Mount exactly one, high in the tree, so it survives navigation instead of restarting on every route.
  • Start from a preset and adjust from there — the presets carry their own size and trail defaults.
  • Leave the native cursor visible over anything clickable, or people lose the affordance they rely on.

Avoid:

  • Do not mount more than one. Each opens its own drawing surface and they stack, which shows as a doubled trail and a halved frame rate.
  • Do not hide the native cursor on a site with forms. A shader blob gives no indication of what is focused or editable.
  • Do not mount it per-page. Every route change tears down the surface and builds a new one, which stutters at exactly the moment the page is busiest.

Props

A cursor trail drawn on the GPU, in four flavours. It replaces the native pointer with a shader that paints where you have just been — a soft aurora ribbon, a neon streak, spreading rings, or sparks. Mount it once near the root of your app; it draws over everything and takes no layout space.

Shared Props

Common to all exported components in this suite.

PropTypeDefaultDescription
colorstring | [number, number, number]"#7EACB5"Trail colour, as a hex string or three 0–1 channel values. An unparseable value falls back to the default rather than throwing.
sizenumberthe preset's own sizeRadius of the cursor head in pixels. Each preset carries its own default — 16 aurora, 18 neon, 24 ripple, 20 sparks.
trailnumberthe preset's own trailHow long the trail lingers, 0–1. Defaults are 0.35 aurora, 0.52 neon, 0.68 ripple, 0.62 sparks. Note that aurora pins this to 0.35 and ignores the value you pass.

<HaloCursor />

The cursor itself. Renders no DOM of its own — it mounts a full-viewport canvas and returns null, so drop it anywhere in the tree.

PropTypeDefaultDescription
presetHaloCursorPresetauroraCursorWhich shader to draw with. Pass one of the four exported presets, or your own object of the same shape.
params[number, number, number, number][0, 0, 0, 0]Four spare values handed straight to the shader. The bundled presets ignore them; they exist for a custom preset to use.
hideNativeCursorbooleantrueHides the system pointer document-wide while the cursor is active. Set it to false to keep both. Only an explicit false disables it.
zIndexnumber9999Stacking order of the canvas. Raise it if a fixed overlay covers the trail.
disableAdaptiveQualitybooleanfalseKeeps the cursor at full resolution on every device. By default the runtime pins the pixel ratio to 1 on the most constrained tier, which softens the trail. This does not affect the reduced-motion and coarse-pointer checks — the cursor still declines to mount at all in either case.

Presets

PresetDescriptionKey Overrides
"auroraCursor"A soft, wide ribbon that drifts behind the pointer.
size=16trail=0.35 (fixed)
"neonCursor"A tight, bright streak with a hard core.
size=18trail=0.52
"rippleCursor"Rings that spread outward from where you move.
size=24trail=0.68
"sparksCursor"Small particles that scatter and fade.
size=20trail=0.62

<AuroraCursor />

HaloCursor with the aurora preset already applied. Same for NeonCursor, RippleCursor and SparksCursor — each accepts only colour, size and trail.

<useHaloCursor />

hook

The hook behind the component. Returns `{ active }` so you can tell whether the cursor actually mounted — it will be false on touch devices and under reduced motion. Options are read once, on mount; later changes are ignored.

PropTypeDefaultDescription
optionsHaloCursorOptions{}The same options HaloCursor takes.

<mountHaloCursor />

Imperative entry point for non-React code. Returns a handle with `active`, `setOptions()` and `destroy()`. Unlike the hook, setOptions updates a live cursor.

PropTypeDefaultDescription
optionsHaloCursorOptions{}The same options HaloCursor takes.
It refuses to start when the visitor prefers reduced motion, and on coarse pointers such as phones and tablets. In both cases you get an inactive handle back rather than an error, and the native cursor is left alone.
Client component. It reads the pointer and opens a GPU drawing context, neither of which exists on the server.
One canvas is shared no matter how many times you mount it — a second instance attaches to the same engine rather than starting another. Frames come from the shared motion runtime, so it yields under load instead of competing.
While active it hides the system pointer everywhere via an injected global style. If part of your UI needs a visible cursor, pass hideNativeCursor={false} and set your own.
However many times you mount it, there is one cursor: one surface, one frame callback, one engine. Mounting a second returns a handle to the first rather than stacking. Resolution follows the device tier. If the browser takes the drawing context away — which it does under memory pressure, and more often on a page with several of these — the component reports it and rebuilds once the context comes back, instead of leaving a permanently black rectangle.
Back to all components