caliper uidocs

Cursor label

Free

A small pill follows the pointer over a labelled area and says what a click does there: Drag, Play, Open. On touch it becomes a label in the corner.

Installs components/ui/cursor-label.tsxDepends on none beyond ReactMotion honours reduced motion
pnpm dlx shadcn@latest add https://caliperui.site/r/cursor-label.json

When to use #

Use it for

  • Galleries, film posters and case-study tiles where the action is not obvious.
  • Large media areas with one clear gesture.

Skip it when

  • Ordinary buttons and links; their own labels already say it.
  • Anywhere the native pointer matters, such as text editing.

Decisions #

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

Decoration over a real name
Every labelled element keeps its own accessible name. The pill is hidden from assistive technology and never carries information that is not also in the page.
The pointer is hidden, by choice
Over a labelled element the pill replaces the native pointer. Pass nativeCursor to keep both.

Styling

Ink pill, grows on press
It uses the primary button's ink and scales to 1.12 while pressed, which is the only feedback it gives.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/cursor-label.json

Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { CursorLabel } from "@/components/ui/cursor-label"

export function CursorLabelDemo() {
  const tile = "relative grid h-36 place-items-center rounded-surface border border-border bg-elevated outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring";
  return (
    <CursorLabel className="grid w-full max-w-xl gap-3 p-4 sm:grid-cols-3">
      <a href="#gallery" data-cursor-label="Drag" aria-label="Gallery, drag to browse" className={tile}>
        <span className="relative h-12 w-[74px]">
          <i className="absolute inset-0 -translate-y-2.5 scale-90 rounded-[10px] bg-muted opacity-60 shadow-[0_0_0_1px_var(--border-strong)]" />
          <i className="absolute inset-0 -translate-y-1 scale-95 rounded-[10px] bg-muted opacity-80 shadow-[0_0_0_1px_var(--border-strong)]" />
          <i className="absolute inset-0 rounded-[10px] bg-elevated shadow-[0_0_0_1px_var(--border-strong)]" />
        </span>
      </a>
      <a href="#film" data-cursor-label="Play" aria-label="Product film, play" className={tile}>
        <span className="grid size-14 place-items-center rounded-full shadow-[inset_0_0_0_1.5px_var(--foreground)]">
          <span className="ms-1 border-y-8 border-s-[13px] border-y-transparent border-s-foreground" />
        </span>
      </a>
      <a href="#case" data-cursor-label="Open" aria-label="Case study, open" className={tile}>
        <span className="grid h-14 w-[86px] content-center gap-2 rounded-lg bg-muted p-3 shadow-[0_0_0_1px_var(--border-strong)]">
          <i className="h-1.5 rounded-full bg-border-strong" />
          <i className="h-1.5 w-2/3 rounded-full bg-border-strong" />
          <i className="h-1.5 w-1/2 rounded-full bg-border" />
        </span>
      </a>
    </CursorLabel>
  );
}

API reference #

CursorLabel

component

Also accepts the native props of its root element and a ref.

PropTypeDefaultDescription
nativeCursorbooleanfalseKeep the native pointer visible beside the label. By default it is hidden over labelled elements.

Accessibility #

It is built on native elements and keeps aria-hidden in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.