Dice
A die that tumbles as you move, lands on six over anything clickable, and rolls while you press.
npx shadcn@latest add https://hoverui.fun/ r/ dice.jsonAdds components/cursors/dice.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
- Toys
- Hook
- useCursor
- Dependencies
- None
- License
- MIT
How it behaves
A die that tumbles as you move and settles when you stop; it lands on six over anything clickable and rolls while you press.
Usage
import { DiceCursor } from "@/components/cursors/dice";
export function Section() {
return (
<section>
<DiceCursor />
{/* this section now wears Dice */}
</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";
/** Pip spots: top left, top right, middle left, centre, middle right, bottom left, bottom right. */
const SPOTS = [
[7, 7],
[17, 7],
[7, 12],
[12, 12],
[17, 12],
[7, 17],
[17, 17],
];
/** Which spots each face lights, from 1 to 6. */
const FACES = [[], [3], [0, 6], [0, 3, 6], [0, 1, 5, 6], [0, 1, 3, 5, 6], [0, 1, 2, 4, 5, 6]];
const roll = (face: number) => {
let next = face;
while (next === face) next = 1 + Math.floor(Math.random() * 6);
return next;
};
/** A die showing `face`; exported so a page can draw it at rest. */
export function DiceArt({ face = 5, className = "" }: { face?: number; className?: string }) {
return (
<svg viewBox="0 0 24 24" className={className} aria-hidden>
<rect x="2" y="2" width="20" height="20" rx="4" fill="none" stroke="currentColor" strokeWidth="1.5" />
{SPOTS.map(([cx, cy], i) => (
<circle key={i} data-pip cx={cx} cy={cy} r="1.9" fill="currentColor" opacity={FACES[face].includes(i) ? 1 : 0} />
))}
</svg>
);
}
/** A die that tumbles as you move and settles when you stop; it lands on six over anything clickable and rolls while you press. */
export function DiceCursor({ className = "" }: { className?: string }) {
const s = useRef({ x: 0, y: 0, lx: 0, ly: 0, travel: 0, face: 5, shown: 5, rot: 0, spin: 0, rolling: 0, bounce: 1, wasDown: false });
const ref = useCursor((p, el) => {
const st = s.current;
const tumble = () => {
st.face = roll(st.face);
st.spin += Math.random() < 0.5 ? 90 : -90;
};
if (!p.dt) [st.lx, st.ly, st.travel] = [p.x, p.y, 0];
st.travel += Math.hypot(p.x - st.lx, p.y - st.ly);
[st.lx, st.ly] = [p.x, p.y];
if (p.down) {
// Shaken in the hand: a new face every 70ms.
st.rolling += p.dt;
if (st.rolling > 0.07 && !p.reduced) {
st.rolling = 0;
tumble();
}
} else if (p.hover) {
if (st.face !== 6) [st.face, st.spin] = [6, st.spin + 90];
} else if (st.travel > 28 && !p.reduced) tumble();
if (st.travel > 28) st.travel = 0;
if (st.wasDown && !p.down) st.bounce = 1.3;
st.wasDown = p.down;
st.bounce += (1 - st.bounce) * p.ease(10);
st.rot += (st.spin - st.rot) * p.ease(14);
st.x += (p.x + 16 - st.x) * p.ease(14);
st.y += (p.y + 16 - st.y) * p.ease(14);
const [die, dot] = el.children as HTMLCollectionOf<HTMLElement>;
die.style.transform = `translate(${st.x}px, ${st.y}px) rotate(${st.rot}deg) scale(${st.bounce})`;
if (st.face !== st.shown) {
st.shown = st.face;
die.querySelectorAll("[data-pip]").forEach((pip, i) => pip.setAttribute("opacity", FACES[st.face].includes(i) ? "1" : "0"));
}
dot.style.transform = `translate(${p.x}px, ${p.y}px)`;
});
return (
<div ref={ref} aria-hidden className={`${cursorRoot} ${className}`}>
<DiceArt className="absolute size-6 -translate-1/2" />
<div className="absolute size-1.5 -translate-1/2 rounded-full bg-current" />
</div>
);
}
It runs on the useCursor hook, which has its own source and API.
More toys cursors
- BalloonA balloon on a string, tugging up and swaying as you move. Puffs up over controls; a click pops it.
- Yo-yoSwings on its string below the pointer. Press to throw it down to sleep; let go to reel it back.
- PlaneA paper plane that glides after the pointer and banks into its turns. It circles controls; a click throws it.
- TopA spinning top that wobbles and teeters when you stop, but never falls. Controls sink into dream levels; a click kicks it.
- LaserThe pointer is a laser dot and a cat chases it: it creeps, wiggles and pounces, bats at controls and naps when you stop.
Made a cursor of your own? Contributed cursors carry their maker's name on their page.
Contribute a cursor