Trail
A tapering chain of dots, each chasing the one ahead. The head swells over anything clickable.
npx shadcn@latest add https://hoverui.fun/ r/ trail.jsonAdds components/cursors/trail.tsx. The CLI also installs the useCursor hook it runs on, once, into your hooks folder. The files are yours from there.
Draw through the empty space, then over the button: each dot chases the one ahead, and the head swells.Each dot chases the one ahead of it. Open this on a computer to draw with it.
- Kind
- Followers
- Hook
- useCursor
- Dependencies
- None
- License
- MIT
Usage
import { TrailCursor } from "@/components/cursors/trail";
export function Section() {
return (
<section>
<TrailCursor />
{/* this section now wears Trail */}
</section>
);
}Put it inside <body> in your root layout to wear it on every page. Nested cursors take over inside their own element.
Props
- className
- Colors the cursor through currentColor, e.g. text-pink-500. To read on any background, pass text-white mix-blend-difference, as this site does.
- length
- How many dots make the chain. Default 14.
- size
- The head dot's diameter, in px; the rest taper from it. Default 12.
- speed
- How quickly each dot catches the one ahead; lower stretches the chain out. Default 30.
- Hover targets
- a, button, input, select, textarea, label, [role=button], [data-cursor-label]
Source
"use client";
import { useRef } from "react";
import { cursorRoot, useCursor } from "@/hooks/use-cursor";
/** A tapering chain of dots, each chasing the one ahead; the head swells over anything clickable. */
export function TrailCursor({
className = "",
length = 14,
size = 12,
speed = 30,
}: {
className?: string;
/** How many dots make the chain. */
length?: number;
/** The head dot's diameter, in px; the rest taper from it. */
size?: number;
/** How quickly each dot catches the one ahead; lower stretches the chain out. */
speed?: number;
}) {
const dots = useRef<{ x: number; y: number }[]>([]);
const swell = useRef(1);
const ref = useCursor((p, el) => {
// The chain follows `length` as it changes: new dots start at the pointer.
while (dots.current.length < length) dots.current.push({ x: p.x, y: p.y });
dots.current.length = length;
swell.current += ((p.down ? 0.7 : p.hover ? 1.8 : 1) - swell.current) * p.ease(16);
let { x, y } = p;
dots.current.forEach((d, i) => {
const k = i ? p.ease(speed) : 1;
d.x += (x - d.x) * k;
d.y += (y - d.y) * k;
({ x, y } = d);
const t = 1 - i / length;
// The swell fades down the chain: full at the head, none at the tail.
(el.children[i] as HTMLElement).style.transform = `translate(${d.x}px, ${d.y}px) scale(${t * (1 + (swell.current - 1) * t)})`;
});
});
return (
<div ref={ref} aria-hidden className={`${cursorRoot} ${className}`}>
{Array.from({ length }, (_, i) => (
<div key={i} className="absolute -translate-1/2 rounded-full bg-current" style={{ width: size, height: size }} />
))}
</div>
);
}
It runs on the useCursor hook, which has its own source and API.
More followers cursors
- RingExact dot, lagging ring. Swells on links, squeezes on click.
- MagnetA dot with a pull: over anything clickable, the control leans toward the pointer and the dot settles between them.
- RimlightA light that catches the edges of the controls near the pointer, brighter the closer they are. The hovered one lights all round; a click sends a ring of light out.
Made a cursor of your own? Contributed cursors carry their maker's name on their page.
Contribute a cursor