Pixel
A pixel cursor on an 8px grid with a stepped, fading trail. Over controls it draws a blinking selection.
npx shadcn@latest add https://hoverui.fun/ r/ pixel.jsonAdds components/cursors/pixel.tsx. The CLI also installs the useCursor hook it runs on, once, into your hooks folder. The files are yours from there.
Move through the preview, then hover the controls and press.Open this on a computer to move it around: it follows a mouse, not a finger.
- Kind
- Retro
- Hook
- useCursor
- Dependencies
- None
- License
- MIT
How it behaves
A pixel cursor on an 8px grid that leaves a stepped, fading trail like an old LCD; over anything clickable it draws a blinking selection, and a click lights a cross.
Usage
import { PixelCursor } from "@/components/cursors/pixel";
export function Section() {
return (
<section>
<PixelCursor />
{/* this section now wears Pixel */}
</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.
- 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";
const CELL = 8;
const COUNT = 40;
/** A pixel cursor on an 8px grid that leaves a stepped, fading trail like an old LCD; over anything clickable it draws a blinking selection, and a click lights a cross. */
export function PixelCursor({ className = "" }: { className?: string }) {
const s = useRef({
trail: Array.from({ length: COUNT }, () => ({ x: 0, y: 0, age: Infinity })),
next: 0,
gx: 0,
gy: 0,
t: 0,
wasDown: false,
});
const ref = useCursor((p, el) => {
const st = s.current;
const [gx, gy] = [Math.floor(p.x / CELL), Math.floor(p.y / CELL)];
const light = (x: number, y: number) => {
Object.assign(st.trail[st.next], { x, y, age: 0 });
st.next = (st.next + 1) % COUNT;
};
if (!p.dt) {
[st.gx, st.gy] = [gx, gy];
for (const c of st.trail) c.age = Infinity;
}
// Every cell the pointer crossed since the last frame, so a fast move leaves no gaps.
if ((gx !== st.gx || gy !== st.gy) && !p.reduced) {
const n = Math.min(Math.max(Math.abs(gx - st.gx), Math.abs(gy - st.gy)), 12);
for (let k = 0; k < n; k++) light(Math.round(st.gx + ((gx - st.gx) * k) / n), Math.round(st.gy + ((gy - st.gy) * k) / n));
}
[st.gx, st.gy] = [gx, gy];
if (p.down && !st.wasDown)
for (const [x, y] of [[-1, 0], [1, 0], [0, -1], [0, 1], [-2, 0], [2, 0], [0, -2], [0, 2]]) light(gx + x, gy + y);
st.wasDown = p.down;
st.t += p.dt;
const els = el.children as HTMLCollectionOf<HTMLElement>;
st.trail.forEach((c, i) => {
c.age += p.dt;
// Three steps down to nothing, not a smooth fade.
els[i].style.opacity = c.age < 0.15 ? "0.7" : c.age < 0.3 ? "0.45" : c.age < 0.45 ? "0.2" : "0";
els[i].style.transform = `translate(${c.x * CELL}px, ${c.y * CELL}px)`;
});
els[COUNT].style.transform = `translate(${gx * CELL}px, ${gy * CELL}px)`;
const select = els[COUNT + 1];
select.style.transform = `translate(${(gx - 1) * CELL}px, ${(gy - 1) * CELL}px)`;
select.style.opacity = p.hover && (p.reduced || Math.floor(st.t * 3) % 2 === 0) ? "1" : "0";
});
return (
<div ref={ref} aria-hidden className={`${cursorRoot} ${className}`}>
{Array.from({ length: COUNT }, (_, i) => (
<div key={i} className="absolute size-2 bg-current opacity-0" />
))}
<div className="absolute size-2 bg-current" />
<div className="absolute size-6 border-2 border-current opacity-0" />
</div>
);
}
It runs on the useCursor hook, which has its own source and API.
More retro cursors
- ClassicThe 1-bit desktop arrow, pixel for pixel: a pointing hand over controls, the hourglass while you press.
- ChompAn arcade chomper that faces your way and eats as it goes. Gobbles controls; snaps shut on press.
- MatrixGreen code rains off your path, pours over anything clickable, and bursts on click.
- SnakeThe phone-game snake, chasing the pointer square by square. It grows over controls and speeds up on press.
- CoinAn arcade coin on the pointer. Every click pops a spinning coin with a "+1" and keeps score; over a control a "?" block hangs above it and the click bumps it.
Made a cursor of your own? Contributed cursors carry their maker's name on their page.
Contribute a cursor