> ## Documentation Index
> Fetch the complete documentation index at: https://docs.collab-kit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Cursor Tracking

> Build a real-time cursor tracking experience with the Presence module.

This guide walks you through building live cursor tracking where each user's mouse position is displayed to all other participants in the room.

## What You'll Build

* Real-time cursor positions for all connected users
* User name labels on each cursor
* Automatic cleanup when users disconnect
* Different cursor colors per user

## Prerequisites

* A CollabKit server running and a room + user created ([Quickstart](/quickstart))
* `@collab-kit/client` and `@collab-kit/utils` installed

## Step 1: Set Up the Client

```typescript theme={null}
import CollabKitClient from '@collab-kit/client';

const client = new CollabKitClient({
  serverUrl: 'https://api.collab-kit.com',
  authToken: '<jwt-token>',
});

await client.join({ role: 'editor' });
```

<Note>
  Presence writes require an editor session. Viewers can receive cursor updates and follow users, but cannot call `presence.update(...)`.
</Note>

## Step 2: Broadcast Cursor Position

Use the [Presence module](/sdk/presence) to share cursor coordinates. Updates are automatically throttled to 50ms:

```typescript theme={null}
document.addEventListener('mousemove', (e) => {
  client.presence.update({
    cursor: {
      x: e.clientX,
      y: e.clientY,
    },
  });
});
```

## Step 3: Render Remote Cursors

Subscribe to all users' presence and create/update DOM elements for each cursor:

```typescript theme={null}
const cursors = new Map<string, HTMLElement>();

// Generate a consistent color from a string
function stringToColor(str: string): string {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash);
  }
  const hue = hash % 360;
  return `hsl(${hue}, 70%, 50%)`;
}

client.presence.sync('*', ({ userId, state }) => {
  // Skip our own cursor
  if (userId === client.userId) return;

  // User disconnected -- remove their cursor
  if (state === null) {
    cursors.get(userId)?.remove();
    cursors.delete(userId);
    return;
  }

  // Create or get the cursor element
  let el = cursors.get(userId);
  if (!el) {
    el = document.createElement('div');
    el.className = 'remote-cursor';
    el.style.position = 'fixed';
    el.style.pointerEvents = 'none';
    el.style.zIndex = '9999';
    el.style.transition = 'transform 0.1s ease-out';

    // Cursor dot
    const dot = document.createElement('div');
    dot.style.width = '8px';
    dot.style.height = '8px';
    dot.style.borderRadius = '50%';
    dot.style.backgroundColor = stringToColor(userId);
    el.appendChild(dot);

    // Name label
    const label = document.createElement('span');
    const user = client.users.active.get(userId) ?? client.users.all.get(userId);
    label.textContent = user?.name ?? userId;
    label.style.fontSize = '12px';
    label.style.marginLeft = '8px';
    label.style.backgroundColor = stringToColor(userId);
    label.style.color = 'white';
    label.style.padding = '2px 6px';
    label.style.borderRadius = '4px';
    label.style.whiteSpace = 'nowrap';
    el.appendChild(label);

    document.body.appendChild(el);
    cursors.set(userId, el);
  }

  // Update position
  el.style.transform = `translate(${state.cursor.x}px, ${state.cursor.y}px)`;
});
```

## Step 4: Clean Up on Disconnect

Remove all cursor elements when the user leaves:

```typescript theme={null}
window.addEventListener('beforeunload', () => {
  void client.disconnect();
});
```

Other clients will receive a `null` state in the presence callback, which triggers cursor removal (handled in Step 3).

## Step 5: Add Typing Indicators (Optional)

Extend the presence state to include a cursor mode:

```typescript theme={null}
// Track typing state
const input = document.querySelector('input');

input?.addEventListener('focus', () => {
  client.presence.update({
    cursor: { x: 0, y: 0, mode: 'typing' },
  });
});

input?.addEventListener('blur', () => {
  client.presence.update({
    cursor: { x: 0, y: 0, mode: 'idle' },
  });
});

// In the presence sync callback, check the mode:
client.presence.sync('*', ({ userId, state }) => {
  if (!state || userId === client.userId) return;

  if (state.cursor.mode === 'typing') {
    showTypingIndicator(userId);
  } else {
    hideTypingIndicator(userId);
  }
});
```

## Next Steps

* Combine cursor tracking with [Follow Mode](/sdk/presence#example-follow-mode) to let users follow each other
* Add cursor click animations using [Broadcasts](/sdk/broadcasts)
* Track scroll position to sync viewports across users
