> ## 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.

# Presence

> Share ephemeral state like cursors, selections, and scroll positions in real time.

The Presence module lets users share short-lived, ephemeral state with other participants. Common use cases include live cursor tracking, text selections, scroll positions, and typing indicators.

Access it via `client.presence`.

<Note>
  Presence data is **not persisted** -- it exists only while users are connected.
</Note>

<Note>
  Only editors can send presence updates. Viewers can receive presence updates and can follow users, but `presence.update(...)` is blocked for viewer sessions.
</Note>

## Methods

### Update Presence

**`update()`**

Broadcast your current presence state to other participants. The input is a freeform object -- you can include any data you want.

```typescript theme={null}
client.presence.update({
  cursor: { x: 100, y: 200 },
  screen: { width: 1920, height: 1080 },
});
```

Updates are **throttled at 50ms** to prevent flooding the WebSocket. If you call `update()` more frequently, the latest state is sent after the throttle window.

```typescript theme={null}
// Track cursor movement -- throttled automatically
document.addEventListener('mousemove', (e) => {
  client.presence.update({
    cursor: { x: e.clientX, y: e.clientY, state: 'idle' },
  });
});

// Track typing state
document.querySelector('input')?.addEventListener('keydown', () => {
  client.presence.update({
    cursor: { x: 0, y: 0, state: 'typing' },
  });
});
```

### Subscribe to Presence Updates

**`sync(target, callback)`**

Subscribe to presence changes from other users. The `target` parameter controls which users you receive updates from:

| Target            | Payload                                                                       | Description                  |
| ----------------- | ----------------------------------------------------------------------------- | ---------------------------- |
| `'*'`             | `{userId: string, state: `[`PresenceState`](/types/presence#presencestate)`}` | All users in the room        |
| `'following'`     | `{userId: string, state: `[`PresenceState`](/types/presence#presencestate)`}` | Only users you are following |
| `userId` (string) | `{userId: string, state: `[`PresenceState`](/types/presence#presencestate)`}` | A specific user by ID        |

```typescript theme={null}
// Subscribe to all users
client.presence.sync('*', ({ userId, state }) => {
  if (state === null) {
    // User left or presence cleared, do something
    return;
  }
  // Update cursor position...
});
```

### Unsubscribe from Presence Updates

**`unsync()`**

Unsubscribe from presence updates:

```typescript theme={null}
client.presence.unsync();
```

### Get All States

**`getStates()`**

Get all current presence states as a map:

```typescript theme={null}
const states = client.presence.getStates();
// Map<string, PresenceState>

for (const [userId, state] of states) {
  console.log(`${userId}:`, state);
}
```

### Get State

**`getState(userId?: string)`**

Get a specific user's presence state, or the current user's state if no ID is provided:

```typescript theme={null}
// Get my own presence state
const myState = client.presence.getState();

// Get another user's presence state
const theirState = client.presence.getState('user-002');
```

## Examples

### Live Cursors

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

// Create or update a cursor element for each user
client.presence.sync('*', ({ userId, state }) => {
  if (userId === client.userId) return; // Skip self

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

  let el = cursors.get(userId);
  if (!el) {
    el = document.createElement('div');
    el.className = 'remote-cursor';
    document.body.appendChild(el);
    cursors.set(userId, el);
  }

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

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

### Follow Users

Use the `'following'` sync target combined with the [follow/unfollow](/sdk/users#follow) user methods to build a "follow mode" where one user's viewport mirrors another's:

```typescript theme={null}
// Follow a user
const targetUser = client.users.active.get('user-002') ?? client.users.all.get('user-002');
await targetUser.follow();

// Subscribe to followed user's presence
const cursorEl = document.createElement('div');
cursorEl.className = 'follow-cursor';
document.body.appendChild(cursorEl);

client.presence.sync('following', ({ userId, state }) => {
  if (state === null) {
    cursorEl.style.display = 'none';
    return;
  }

  const user = client.users.active.get(userId) ?? client.users.all.get(userId);
  cursorEl.style.display = 'block';
  cursorEl.style.left = `${state.cursor.x}px`;
  cursorEl.style.top = `${state.cursor.y}px`;
  cursorEl.title = user?.name ?? userId;
});

// Unfollow when done
await targetUser.unfollow();
```
