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.
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 UtilityPreview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
pnpm dlx shadcn@latest add https://ui.vllnt.com/r/live-cursor.jsonStorybook
Explore all variants, controls, and accessibility checks in the interactive Storybook playground.
View in Storybook4 stories available:
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";
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.