Skip to content
Template Empire
Browse components

Cursor Image Trail

A free cursor image trail react effect with a fixed node pool and no per-move layout reads.

Archive 07 / High Atlas

Eleven days above the treeline

Forty-one plates from the spring crossing: ridgelines at first light, the dune sea after rain, and the survey camp under a hard, clear sky.

Photographs by Lena Aldous / Imlil to Lake Ifni, 92 km

Issue 07, edition of 250, numbered and stamped

Order issue 07

Customise

6
52px
0.08
112px
0.65s
7

Code

<CursorImageTrail images={images} />

Cursor Image Trail is a free cursor image trail react effect for Next.js, and the interesting part is what it refuses to do. The node count is fixed: trailLength images are rendered once and reused in order, so a pointer sweeping the panel for a minute allocates exactly as much as one that crosses it once. An emission cancels whatever fade the node it reaches was running rather than stacking another on top.

The pointer path costs nothing per move either. The root's bounds are cached on entry and refreshed by a ResizeObserver, so pointermove reads event coordinates and nothing else - no getBoundingClientRect, no forced layout, no React state. Emission is by Euclidean distance rather than by time, so a slow pointer leaves fewer images rather than a denser clump, and nothing anywhere is random: the image order and the alternating rotation are both derived from the pool index. The whole effect is gated on one media query for a fine pointer and no reduced-motion preference, so on a coarse pointer or with reduced motion nothing is created at all - no animations, no observers, no listeners. Each fade is a single Web Animation run on the compositor, so the main thread does no per-frame work while the trail fades and nothing keeps running afterwards.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/cursor-image-trail

Usage

import { CursorImageTrail } from "@/components/cursor-image-trail";

const images = [
  { src: "/trail/one.svg", width: 1600, height: 1000 },
  { src: "/trail/two.svg", width: 1600, height: 1000 },
  { src: "/trail/three.svg", width: 1600, height: 1000 },
];

export default function Page() {
  return (
    <div className="relative overflow-hidden">
      <div className="pointer-events-none p-16">Your decorative copy</div>
      <CursorImageTrail images={images} className="absolute inset-0" />
    </div>
  );
}

Props

PropTypeDefaultDescription
imagesReadonlyArray<{ src: string; width: number; height: number }>requiredTwo to eight decorative images. Both dimensions are required and must be real numbers: they reserve each node's box before the file arrives. An entry missing either is dropped, and fewer than two valid images disables the effect entirely.
trailLengthnumber6How many nodes exist, clamped to 2-10. This is the whole pool, not a maximum: the same nodes are reused in order for as long as the pointer keeps moving.
spacingnumber52How far the pointer must travel before the next node is emitted, in CSS pixels, clamped to 24-120. Measured as Euclidean distance, so diagonal movement emits at the same rate as straight.
scaleDecaynumber0.08How much a node shrinks over its lifetime, clamped to 0-0.16, multiplied by the pool size and floored at 0.4 so a long trail cannot collapse to nothing.
imageSizenumber112Maximum trail image width in CSS pixels, clamped to 64-180.
fadeDurationnumber0.65How long a node takes to fade out, in seconds, clamped to 0.3-1.2.
rotationnumber7Maximum rotation in degrees, clamped to 0-16. It alternates by pool index rather than varying randomly, so the trail is identical every time.
blendMode"normal" | "multiply" | "screen""normal"Decorative CSS blend mode for the nodes. It carries no information; nothing about the effect's meaning depends on how it blends.
classNamestringundefinedAdditional classes merged onto the bounded effect root with cn(). This is where you size and position it.

Customisation

Cursor Image Trail declares two tokens of its own, each falling back to a global semantic token: --image-trail-outline for the node edge and --image-trail-shadow as the source colour for a low-opacity shadow. Geometry and timing come from props rather than tokens, because they are clamped. The images themselves are yours, and nothing about them is themed.

:root {
  --image-trail-outline: oklch(from var(--border) l c h / 0.5);
  --image-trail-shadow: var(--foreground);
}

<CursorImageTrail images={images} trailLength={9} spacing={36} blendMode="screen" />

Frequently asked questions

Is Cursor Image Trail free for commercial use?
Yes. Cursor Image Trail is MIT licensed, so you can use it in personal and commercial projects without attribution.
Should I put the trail over buttons and links?
Yes, content beneath the trail stays interactive. The trail root is pointer-events-none and its parent receives the pointer for it, so text under the field stays selectable and links and buttons stay clickable and focusable. A move counts only inside the root's own box, so give the root the area you want the trail in; the images fade over whatever is beneath, so keep them clear of copy that must stay legible.
What does it do on a phone, or with reduced motion?
Nothing at all, and that is stronger than it sounds. The entire effect is built inside one media query gated on a fine pointer and no reduced-motion preference. Outside that query there are no animations, no ResizeObserver, no IntersectionObserver, no pointer listeners and no visibility listener - not a disabled effect but an absent one, so a phone never pays for machinery it cannot use. The same applies when fewer than two valid images are supplied. The operating system's cursor is never hidden or replaced either, so a reader using a large or high-contrast pointer keeps it.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates