No.141 · Cursors
Intent Label
The cursor says what a click will do. Over anything marked data-intent it grows from a dot into a pill — View case study, Play reel · 0:42, Drag, Copy email — whose width springs to each new label. Dragging nudges its arrows; a playing reel draws progress along its edge.
The prompt
The design brief behind this component — reuse it to regenerate, remix, or explain it.
Build a scoped contextual cursor: a wrapper component that reads data attributes on its descendants and turns the pointer into a pill naming the action under it. Markup contract: data-intent="Label", data-intent-icon (view | play | pause | drag | external | copy | check), optional data-intent-progress (0–1) and data-intent-drag (the element is dragged sideways). Scope: under (hover: hover) and (pointer: fine) the host gets data-live → cursor:none on it and every descendant (!important), fields excepted, where the drawn cursor fades out. The layer is aria-hidden, pointer-events:none, absolute over the children; coordinates are host-local and corrected by rect.width / offsetWidth for scaled hosts. The cursor is one element, centred on the pointer, its position written in the pointermove handler. With no intent it is a 10px dot (8px pressed) with a hairline ring. Over an intent it becomes a 40px-tall pill: the label and its 15px icon are written into the idle one of two absolutely-centred slots and cross-faded (opacity, blur 5px → 0, scale 0.9 → 1 with a slight overshoot, 40ms delay in), while a hidden measuring span with the same font gives the new width + 30px. Width and height are springs (k 340, ζ 0.78), so moving from "Drag" to "View case study" visibly stretches the pill. The pill leans up to 7° into horizontal speed and rights itself. One rAF loop runs only while something is moving. States: pressing a data-intent-drag element spreads the drag arrows (scaleX 1.22) and nudges them 3px toward the direction you're pulling, until release. data-intent-progress drives a 2px bar along the pill's bottom edge (scaleX). A MutationObserver on data-intent, -icon and -progress updates the pill live while you hover, so a reel can count down ("Play reel · 0:42" → "Pause · 0:17") and a copy button can confirm ("Copied" with a check). Keyboard: a :focus-visible element with data-intent shows the pill at its lower-right corner, so the action is never pointer-only. Night (cream pill) and Paper (ink pill).
Notes
- Interaction
- Move across the case studies, the reel, the press strip and the links. Click the reel to play it, drag the strip, copy the email.
- Motion
- Width/height springs (k 340, ζ 0.78); label cross-fade with blur 200–280ms; lean into horizontal speed; drag arrows 200ms.
- Accessibility
- Every target is a real link, button or focusable region with its own accessible name; the pill only repeats it visually and is aria-hidden. Keyboard focus shows the same label beside the element. Status changes are announced by the page's own live region. Reduced motion removes the blur, overshoot and lean; widths change at once.
- Responsive
- The grid stacks under 640px; the pill is host-scoped and works in scaled previews.
- On touch
- Touch shows no pill and keeps native behaviour: the strip scrolls natively and every target is a normal tap.