Live Cursor

Show remote users' cursors on a shared canvas in your React app, positioned by x/y with a name and status chip. Pointer-events safe. Install via the shadcn CLI.

Report a bug

What it is & when to use

Live Cursor renders a remote user's pointer on a shared canvas or document, positioned by x and y pixel coordinates, with an optional name-and-status chip beside it. It is pure presentation: your app owns the realtime stream and maps each user to a color, then feeds coordinates in. The wrapper is pointer-events none, so it never blocks the interactions underneath. Use it to build multiplayer cursors for collaborative editors, whiteboards, and design tools.

Browse all Utility

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/live-cursor.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; import { cn } from "../../lib/utils"; /** * Localizable strings. * * @public */ export type LiveCursorLabels = { /** Aria-label override. Defaults to `"Live cursor"`. */ region?: string; }; const DEFAULT_LABELS = { region: "Live cursor", } as const satisfies Required<LiveCursorLabels>; /** * Props for {@link LiveCursor}. * * @public */ export type LiveCursorProps = { /** Tailwind / arbitrary CSS color used for the pointer + name chip. Defaults to `var(--foreground)`. */ color?: string; /** Localizable strings. */ labels?: LiveCursorLabels; /** Display name shown in the chip. Pass `null` to hide the chip. */ name?: ReactNode; /** Optional secondary line in the chip (e.g. status, role). */ status?: ReactNode; /** Cursor X in canvas pixels. */ x: number; /** Cursor Y in canvas pixels. */ y: number; } & ComponentPropsWithoutRef<"div">; /** * Remote user's cursor rendered at canvas coordinates with an optional * name + status chip. Pure presentation; the host owns the websocket * stream + maps user ids to colors. * * The wrapper is `pointer-events: none` so host gestures pass through. * * @example * ```tsx * <div className="relative h-screen w-screen"> * <Canvas /> * <LiveCursor x={420} y={180} name="Bea" color="#5b8def" /> * </div> * ``` * * @public */ export const LiveCursor = ({ ref, ...props }: LiveCursorProps & { ref?: React.Ref<HTMLDivElement> }) => { const { className, color, labels, name, status, x, y, ...rest } = props; const resolvedLabels = { ...DEFAULT_LABELS, ...labels }; const resolvedColor = color ?? "var(--foreground)"; return ( <div aria-label={ typeof name === "string" ? `${resolvedLabels.region}: ${name}` : resolvedLabels.region } className={cn( "pointer-events-none absolute z-30 flex items-start gap-1", className, )} data-live-cursor ref={ref} role="img" style={{ left: x, top: y }} {...rest} > <svg aria-hidden="true" className="-ml-1 -mt-1 drop-shadow-sm" data-live-cursor-pointer fill={resolvedColor} height={18} viewBox="0 0 16 18" width={16} > <path d="M0 0 L0 14 L4 11 L7 17 L10 16 L7 10 L13 10 Z" /> </svg> {name === null || name === undefined ? null : ( <span className="ml-2 mt-2 inline-flex flex-col rounded-md px-1.5 py-0.5 text-[10px] font-medium text-white shadow-sm" data-live-cursor-chip style={{ backgroundColor: resolvedColor }} > <span>{name}</span> {status ? ( <span className="text-[9px] opacity-80" data-live-cursor-status> {status} </span> ) : null} </span> )} </div> ); }; LiveCursor.displayName = "LiveCursor";

Dependencies

  • @vllnt/ui@^0.3.0

Frequently asked questions

What is Live Cursor?
Live Cursor is a React component that draws another user's cursor at given canvas coordinates, with an optional name and status chip. It is presentation-only — you supply the position, color, and name from your realtime layer.
How do I add Live Cursor to my app?
Run `pnpm dlx shadcn@latest add https://ui.vllnt.com/r/live-cursor.json`, then render `<LiveCursor x={x} y={y} name="Bea" color="#5b8def" />` inside a positioned container.
Does Live Cursor block clicks on the canvas underneath?
No. The cursor wrapper uses pointer-events none, so gestures pass through to the canvas. It also sets `role="img"` with an aria-label derived from the user's name for screen readers.