Caret
A text caret sized to the line beneath it that blinks when you stop. A block caret over controls.
npx shadcn@latest add https://hoverui.fun/ r/ caret.jsonAdds components/cursors/caret.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
- Marks
- Hook
- useCursor
- Dependencies
- None
- License
- MIT
How it behaves
A text caret, sized to the line height of whatever text it is over, that blinks when you stop; over anything clickable it becomes a block caret.
Usage
import { CaretCursor } from "@/components/cursors/caret";
export function Section() {
return (
<section>
<CaretCursor />
{/* this section now wears Caret */}
</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 text caret, sized to the line height of whatever text it is over, that blinks when you stop; over anything clickable it becomes a block caret. */
export function CaretCursor({ className = "" }: { className?: string }) {
const s = useRef({ w: 2, h: 20, target: null as Element | null, line: 20, font: 16, lx: 0, ly: 0, still: 0 });
const ref = useCursor((p, el) => {
const st = s.current;
// Read the type under the pointer only when the element under it changes.
if (p.target !== st.target && p.target) {
st.target = p.target;
const cs = getComputedStyle(p.target);
st.font = parseFloat(cs.fontSize) || 16;
st.line = Math.min(Math.max(cs.lineHeight === "normal" ? st.font * 1.2 : parseFloat(cs.lineHeight) || st.font * 1.2, 12), 96);
}
const k = p.ease(18);
st.h += (st.line - st.h) * k;
st.w += ((p.hover ? st.font * 0.6 : p.down ? 3 : 2) - st.w) * k;
// Solid while moving; blinks at the editor's half-second once you stop.
st.still = p.x === st.lx && p.y === st.ly ? st.still + p.dt : 0;
[st.lx, st.ly] = [p.x, p.y];
const on = p.reduced || p.down || st.still < 0.5 || Math.floor((st.still - 0.5) / 0.53) % 2 === 1;
const bar = el.firstElementChild as HTMLElement;
bar.style.transform = `translate(${p.x - st.w / 2}px, ${p.y - st.h / 2}px) scale(${st.w}, ${st.h})`;
bar.style.opacity = on ? "1" : "0";
});
return (
<div ref={ref} aria-hidden className={`${cursorRoot} ${className}`}>
<div className="absolute size-px origin-top-left bg-current" />
</div>
);
}
It runs on the useCursor hook, which has its own source and API.
More marks cursors
- RegisterA printer's registration mark. Exact crosshair; the ring opens and fills over anything clickable.
- FrameViewfinder corners around the pointer that snap to frame whatever clickable thing you hover.
- StampA rubber stamp that lifts over controls and leaves a fading inked seal on every click.
- HalftoneA halftone dot screen round the pointer. Over controls it turns to the next ink's angle; hold to spread the ink, click to pulse.
- PenA fountain pen that writes your path in ink, thick and thin as the nib turns, fading as it dries. It underlines controls with a flourish; a click leaves a blot.
Made a cursor of your own? Contributed cursors carry their maker's name on their page.
Contribute a cursor