Readout
A drafting crosshair with live coordinates. Over a control, it reads the tag and size.
npx shadcn@latest add https://hoverui.fun/ r/ readout.jsonAdds components/cursors/readout.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
- Mechanical
- Hook
- useCursor
- Dependencies
- None
- License
- MIT
How it behaves
A drafting crosshair with live coordinates in its area; over a control it reads the control's tag and size.
Usage
import { ReadoutCursor } from "@/components/cursors/readout";
export function Section() {
return (
<section>
<ReadoutCursor />
{/* this section now wears Readout */}
</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";
/** A drafting crosshair with live coordinates in its area; over a control it reads the control's tag and size. */
export function ReadoutCursor({ className = "" }: { className?: string }) {
const text = useRef("");
const ref = useCursor((p, el) => {
const [cross, label] = el.children as HTMLCollectionOf<HTMLElement>;
let next: string;
if (p.hover) {
const r = p.hover.getBoundingClientRect();
next = `${p.hover.tagName.toLowerCase()} ${Math.round(r.width)}×${Math.round(r.height)}`;
} else {
const a = el.parentElement!.getBoundingClientRect();
next = `x ${Math.round(p.x - a.left)} y ${Math.round(p.y - a.top)}`;
}
if (next !== text.current) label.textContent = text.current = next;
// Near the right or bottom edge, the label flips to the other side of the crosshair to stay on screen.
const lx = p.x + 14 + label.offsetWidth > innerWidth ? p.x - 14 - label.offsetWidth : p.x + 14;
const ly = p.y + 30 > innerHeight ? p.y - 30 : p.y + 14;
cross.style.transform = `translate(${p.x}px, ${p.y}px)`;
label.style.transform = `translate(${lx}px, ${ly}px)`;
});
return (
<div ref={ref} aria-hidden className={`${cursorRoot} ${className}`}>
<svg viewBox="0 0 40 40" className="absolute size-10 -translate-1/2 overflow-visible">
<path d="M20 0v14M20 26v14M0 20h14M26 20h14" stroke="currentColor" strokeWidth="1" />
</svg>
<span className="absolute font-mono text-[11px] leading-4 tracking-[0.06em] whitespace-pre" />
</div>
);
}
It runs on the useCursor hook, which has its own source and API.
More mechanical cursors
- GearA gear that rolls as you travel. Over anything clickable, a second gear meshes in.
- CompassThe needle swings to your heading, overshoots, and settles north. It spins over anything clickable.
- ClockKeeps your visitor's real time. The hands race forward over controls and wind back on press.
- RadarA scope sweeping round the pointer. Over a control, it sweeps faster and pings a contact on it.
Made a cursor of your own? Contributed cursors carry their maker's name on their page.
Contribute a cursor