Cursor Engine
Audience: users.
Cursors synchronize pointer position across peers.
Access
Section titled “Access”const cursors = room.useCursors<{ tool: 'pen' | 'eraser' }>();Interface
Section titled “Interface”interface CursorEngine<TCursor extends CursorData = CursorData> { mount(el: HTMLElement): void; unmount(): void; render(options?: CursorRenderOptions): void; subscribe(cb: (positions: CursorPosition<TCursor>[]) => void): Unsubscribe; getPositions(): CursorPosition<TCursor>[]; setPosition(position: Partial<CursorPosition<TCursor>>): void;}Position Shape
Section titled “Position Shape”type CursorData = Record<string, unknown>;
type CursorPosition<TCursor extends CursorData = CursorData> = { userId: string; name: string; color: string; x: number; y: number; xAbsolute: number; yAbsolute: number; element?: string; idle: boolean;} & Partial<TCursor>;Extra cursor fields are preserved across peer sync as long as they are serializable. Reserved runtime fields such as userId, name, color, x, y, xAbsolute, yAbsolute, element, and idle remain owned by the cursor engine and room transport layer.
Render Options
Section titled “Render Options”cursors.render({ container: '#canvas', style: 'default', showName: true, showIdle: false, idleTimeout: 3000, zIndex: 9999, onMount: (element) => element.classList.add('peer-cursor'),});Built-in renderer styles ('default' | 'arrow' | 'dot' | 'pointer' | 'none'):
default: SVG arrow cursor with a name label.arrow: SVG arrow cursor with a name label.dot: compact dot marker with an optional label.pointer: compact pointer marker with an optional label.none: disables the built-in renderer while keeping cursor tracking active.- Unknown style strings fall back to
default.
Render options:
idleTimeout(ms) overrides the idle threshold used by the renderer.onMount(element)is called when each peer cursor element is first created, allowing custom decoration of the rendered node.
Renderer behavior:
- Cursor nodes are absolutely positioned inside the render container.
showName === falsehides the built-in name label.showIdle === falsehides idle peers until they move again.showIdle !== falsekeeps idle peers rendered and marks them idle in the DOM.- Built-in cursor movement uses CSS transitions for smooth interpolation.
CursorOptions.smoothing(defaulttrue) toggles this CSS-transition interpolation.
Custom Renderer Pattern
Section titled “Custom Renderer Pattern”cursors.mount(document.getElementById('board') as HTMLElement);
cursors.subscribe((positions) => { for (const pos of positions) { const id = `cursor-${pos.userId}`; let el = document.getElementById(id); if (!el) { el = document.createElement('div'); el.id = id; document.getElementById('board')?.appendChild(el); }
el.style.position = 'absolute'; el.style.left = `${pos.x * 100}%`; el.style.top = `${pos.y * 100}%`; el.textContent = pos.name; }});Performance Boundaries
Section titled “Performance Boundaries”- Throttle high-frequency cursor updates.
- Keep cursor payloads small.
- Use awareness for semantic state, not pointer telemetry.