Skip to content

Smooth Cursor

A soft dot or ring that trails the real pointer with easing, inside a container or across the page. The system cursor stays unless you ask to hide it.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

Move the mouse over this area.

import { SmoothCursor } from "@rdloom/react";

export default function SmoothCursorBasicExample() {
  return (
    <div className="flex w-full justify-center">
      <SmoothCursor className="flex h-48 w-full max-w-md items-center justify-center rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-subtle)]">
        <p className="text-sm text-[var(--rd-color-text-muted)]">Move the mouse over this area.</p>
      </SmoothCursor>
    </div>
  );
}

Installation

npx rdloom add smooth-cursor

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/smooth-cursor.json

Works, but without upgrade tracking, and you import the motion CSS yourself.

Usage

import { SmoothCursor } from "@rdloom/react";

<div className="flex w-full justify-center">
  <SmoothCursor className="flex h-48 w-full max-w-md items-center justify-center rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-subtle)]">
    <p className="text-sm text-[var(--rd-color-text-muted)]">Move the mouse over this area.</p>
  </SmoothCursor>
</div>

Ring in a card

Studio plan

A slow ring trails the pointer. Your own pointer stays visible.

import { SmoothCursor } from "@rdloom/react";

export default function SmoothCursorRingExample() {
  return (
    <div className="flex w-full justify-center">
      <SmoothCursor
        shape="ring"
        size={36}
        smoothing={0.08}
        className="w-full max-w-md rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-6"
      >
        <h3 className="text-base font-medium">Studio plan</h3>
        <p className="mt-1 text-sm text-[var(--rd-color-text-muted)]">A slow ring trails the pointer. Your own pointer stays visible.</p>
        <button
          type="button"
          className="mt-4 rounded-[var(--rd-radius-control)] bg-[var(--rd-color-action-primary)] px-3 py-1.5 text-sm text-[var(--rd-color-action-on-primary)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--rd-color-focus-ring)]"
        >
          Choose plan
        </button>
      </SmoothCursor>
    </div>
  );
}

Page wide

With scope set to page, a small dot trails the pointer everywhere in the window. Touch and reduced motion never see it.

import { SmoothCursor } from "@rdloom/react";

export default function SmoothCursorPageWideExample() {
  return (
    <div className="flex w-full justify-center">
      <SmoothCursor scope="page" size={14} color="var(--rd-color-action-primary)" />
      <p className="max-w-sm text-center text-sm text-[var(--rd-color-text-muted)]">
        With scope set to page, a small dot trails the pointer everywhere in the window. Touch and reduced motion never see it.
      </p>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
children

Content of the container. With scope "page" the children are rendered as they are and the follower covers the whole window.

nodenone
scope

Where the follower lives: inside the wrapper around the children, or over the whole window.

"container" | "page""container"
shape

A filled dot or an open ring.

"dot" | "ring""dot"
size

Diameter in pixels.

number20
color

Any CSS colour. Defaults to the primary action colour.

stringnone
smoothing

How quickly the follower catches up, from 0.02 (lazy) to 1 (no lag).

number0.15
hideNativeCursor

Hides the system pointer over the container once a mouse is in use. Off by default: keep it off unless the follower is clearly visible everywhere.

booleanfalse
isStill

Switches the follower off and leaves the system pointer alone.

booleanfalse

Accessibility

Role presentation, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

Not interactive.

Screen readers announce

  • Not announced: the follower is purely visual

What your code must do

  • Decoration for mouse users: the follower is aria-hidden, ignores the pointer (pointer-events none) and never takes focus
  • It never replaces keyboard focus: focus rings are untouched and nothing depends on where the follower is
  • Not shown for touch or pen input, for prefers-reduced-motion, with isStill, or inside rdm-still
  • The system pointer is only hidden when hideNativeCursor is set, and only after a mouse has moved over the container
  • The frame loop runs only while the follower is catching up and stops when it arrives; listeners are removed on unmount
  • Forced-colors: the follower keeps its own colour (forced-color-adjust none) so it stays visible

Guidelines

Use it when

  • A playful hero or showcase area for mouse users
  • A soft highlight that follows the pointer over a canvas or gallery

Avoid it when

  • Forms, dashboards and anywhere precise pointing matters
  • Anything that would be unusable without the follower

Don't

  • Hiding the system pointer over text fields and small controls
  • Using it as the only hover feedback
  • Very large or fully opaque followers that cover what is being pointed at

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-action-primary