Detent web components
By Shane Logsdon

Media & effects

Cursor Follower

Implemented

Spring-damped pointer follower with custom icon content

detent-cursor-follower

Port status
Catalog status
implemented
Origin
upstream
Phase
4
Wave
phase-4-pointer
Group
Media & effects

4.5 KB gzip for the class module · inlines the shared spring

Variants

Move the pointer anywhere on this page: each follower tracks it with the pinned spring (stiffness 450, damping 32, mass 0.5) and scales to 1.15 while the pointer is moving. The first follower on this page paints the built-in ring; the ones below render slotted content. The last two opt out of the default-true flags through the property, because a present attribute would mean true.

View soft spring no scale stays visible

With prefers-reduced-motion: reduce every follower maps 1:1 onto the pointer, drops the velocity scale and schedules no frame. Touch input never moves them: a follower is a hover-only affordance.

Recorded states

  • reduced motion recorded state; no setter

Usage

Import

import '/components/cursor-follower/define.js';

// or register it yourself
import { DetentCursorFollower } from '/components/cursor-follower/cursor-follower.js';

Attributes

  • damping
  • hide-on-leave
  • mass
  • offset-x
  • offset-y
  • scale-on-move
  • size
  • stiffness

Properties

No properties are recorded for this component.

Slots

  • default

Events

No events are recorded for this component.

Example

Runnable example: site/examples/cursor-follower.ts. The snippet below is generated from the catalogued attributes.

<detent-cursor-follower
  damping="32"
  hide-on-leave
  mass="0.5"
  offset-x="0"
  offset-y="0"
  scale-on-move
  size="40"
  stiffness="450"
></detent-cursor-follower>

Source

The pinned upstream source for this row:

  • src/components/ui/CursorFollower.tsx sha256 eaa887d42fdc…

Native files

The native module and its artifacts:

  • src/components/cursor-follower/cursor-follower.ts
  • src/components/cursor-follower/styles.css
  • src/components/cursor-follower/define.ts
  • docs/components/cursor-follower.md
  • site/examples/cursor-follower.ts
  • tests/browser/cursor-follower.spec.ts
  • evidence/components/cursor-follower.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
dampingdampingnumber32Kebab-case attribute; camelCase property.
hide-on-leavehideOnLeavebooleantruePresence semantics: present means true, absent means false.
massmassnumber0.5Kebab-case attribute; camelCase property.
offset-xoffsetXnumber0Kebab-case attribute; camelCase property.
offset-yoffsetYnumber0Kebab-case attribute; camelCase property.
scale-on-movescaleOnMovebooleantruePresence semantics: present means true, absent means false.
sizesizenumber40Kebab-case attribute; camelCase property.
stiffnessstiffnessnumber450Kebab-case attribute; camelCase property.

Properties

No property-only values are recorded for this component.

Events

No custom events are recorded for this component.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • The catalog documents a size default of 40 while the pinned source destructures 32; the catalog row is the API authority, so the native default is 40. Owner confirmed the catalog value on 2026-10-29.
  • Touch and other non-hover pointers never drive the follower (upstream attached window pointermove for every pointer type).
  • The built-in ring uses --detent-cursor-follower-ring (#6b7280) instead of the upstream 30% text colour, which measured 1.9:1 on white (accessibility correction).
  • aria-hidden moved from the moving div to the host.

What it does

A fixed-position follower that springs after the pointer and renders the consumer's slotted content at its centre.

Upstream behaviour
  • Configurable spring dynamics (stiffness, damping, and mass)
  • Dynamic velocity scaling when pointer is in motion
  • Automatic viewport departure detection and graceful exit
  • Reduced motion support with instant 1:1 hardware coordinate mapping
  • Supports any custom React icon, text pill, or visual indicator
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • The catalog documents a size default of 40 while the pinned source destructures 32; the catalog row is the API authority, so the native default is 40. Owner confirmed the catalog value on 2026-10-29.
  • Touch and other non-hover pointers never drive the follower (upstream attached window pointermove for every pointer type).
  • The built-in ring uses --detent-cursor-follower-ring (#6b7280) instead of the upstream 30% text colour, which measured 1.9:1 on white (accessibility correction).
  • aria-hidden moved from the moving div to the host.
Previous: Command Menu Next: Date Picker

Docs

Components

Project