Motion pattern

Sticky Cursor

A sticky cursor replaces or augments the native pointer with a custom element that follows the mouse with smoothing, and reacts to what it hovers, growing over links, snapping to buttons, showing a label. It signals a crafted, considered site. Built with GSAP quickTo so the follow stays smooth without per-frame allocation.

Updated June 2, 2026

Components

Components that solve this

Tradeoffs

Tuning the pattern

Follow smoothing
Custom Cursor defaults `cursor-speed` to 0.7 with `expo.out`, which gives a short, premium catch-up; a true 1:1 follow tracks every micro-tremor and reads cheap, while pushing past roughly 0.8s of lag reads as broken, so keep the smoothing in the 0.4 to 0.7s band where it hides jitter without falling behind.
Hover reactions
React to a small, consistent set, grow over links and swap a label over media (Custom Cursor exposes `setLabel` and `clearLabel` for exactly this); reacting to every element turns the cursor into noise, so the restraint line is to pick two or three meaningful states and let everything else pass through unchanged.
Native cursor
Hiding the native cursor (Custom Cursor `hide-cursor` true) is the fully crafted look but only safe on expressive surfaces, never on forms or dense UI; always keep a real fallback, and Custom Cursor already disables itself on touch and tablet (`disable` mobile, tablet) since there is no pointer to follow there.
When

When a sticky cursor fits

A custom cursor sets a tone of craft and intentionality. It works on sites where the experience is part of the product.

  • Portfolio and agency sites where the cursor is part of the brand
  • Product launches and campaign pages that want a signature feel
  • Interactive showcases where the cursor reveals or labels content
  • Editorial pieces with media that the cursor can preview or scrub

Never hide the cursor on functional UI

On forms, dashboards, and dense interactive surfaces a custom cursor adds friction and hurts accessibility. Reserve it for expressive, exploratory surfaces. Always provide a real cursor fallback on touch and for reduced-motion users.
Mechanics

How the follow works

The cursor element is position: fixed and follows the pointer through two gsap.quickTo setters, one for x, one for y, with a short smoothing duration. Hover reactions are class or state changes that scale the cursor or swap its label, driven by mouseenter / mouseleave on interactive targets.

script.js
In production

Annnimate components that solve this pattern

  • The Custom Cursor component handles the smooth follow, hover-state reactions, label support, and a reduced-motion / touch fallback. It exposes an imperative API (setLabel, clearLabel) for programmatic control
  • The Image Trail is a cursor-driven cousin: instead of one follower, it spawns a trail of images that chase the pointer
FAQ

Common questions

Why does my custom cursor stutter?
Creating a new tween on every mousemove. Use two gsap.quickTo setters (x and y) created once - they reuse a single tween instance per axis and just update the target value each event. That's the difference between smooth and stuttery at 60+ mousemoves per second.
How do I keep a custom cursor accessible?
Keep the native cursor visible (or provide a clear fallback), never hide focus indicators, and make sure every interaction works with keyboard alone. Honor prefers-reduced-motion by disabling the smoothing or skipping the custom cursor entirely. On touch devices the custom cursor should not render at all.
Should the follow be 1:1 or lagged?
A short lag (0.3-0.4s smoothing) reads as 'premium' and hides micro-jitter. A true 1:1 follow can feel cheap because it tracks every tiny tremor. But too much lag (0.8s+) feels broken, like the cursor can't keep up. The 0.3-0.5s range is the sweet spot.
Related patterns

Adjacent ideas in the patterns