apps/docs/content/sdk-features/user-following.mdx
User following lets you track a collaborator's viewport. When you follow someone, your camera moves to match their view, including page changes and zoom level. The editor handles follow chains (A follows B who follows C) and interpolates your viewport toward the target on each frame.
Following works with the collaboration presence system, so it needs a collaborative store. User ids are branded TLUserId values: take them from presence records, or create one with createUserId. The default People menu already exposes this: clicking a collaborator zooms to them, and the follow button beside their name starts or stops following. The API below is what it calls.
import { createUserId } from 'tldraw'
const userId = createUserId('user-abc-123')
editor.startFollowingUser(userId)
editor.stopFollowingUser()
editor.zoomToUser(userId)
Following stops when you interact with the canvas or when the followed user disconnects.
When you call Editor#startFollowingUser, the editor stores the target user ID in followingUserId on the instance state and sets up two reactive processes: one watches for page changes and switches pages when the followed user navigates, and another runs on every frame to interpolate your camera toward their viewport.
The frame handler calculates the difference between your current viewport and the target, then interpolates using your animation speed preference. The camera moves quickly at first and slows as it approaches. Once the viewport difference drops below the followChaseViewportSnap threshold (default 2 pixels), the editor locks onto the target and mirrors their viewport exactly instead of interpolating. If the animation speed preference is 0, it locks on immediately.
The editor reads the followed user's camera state and screen bounds from their presence record, constructs their viewport in page space, then resizes your viewport to contain theirs while maintaining your screen's aspect ratio. You can see everything the followed user sees, regardless of screen size.
The interpolation uses lerp on viewport bounds rather than camera values directly, which produces more natural movement when zoom levels differ. The interpolation factor clamps between 0.1 and 0.8 based on the animation speed preference, so following is never sluggish or aggressive.
Page changes happen separately from camera movement. A reactive effect watches the followed user's currentPageId and immediately switches pages when it changes. The page switch uses a direct store update rather than setCurrentPage() to avoid triggering the automatic follow-stopping behavior that occurs on manual page changes.
When switching pages to follow a user, the editor pauses camera interpolation until the page switch completes. This avoids jarring camera movements during page transitions.
Start following a user's viewport. Pass a user ID to set up frame handlers that track their camera and page.
editor.startFollowingUser(userId)
The editor automatically stops following any previously followed user before starting the new follow. If it can't find the target user's presence, the method returns early without starting follow mode.
Editor#stopFollowingUser stops following and returns control to the local user. This commits the current camera position to the store, clears followingUserId from instance state, and emits the editor's stop-following event.
editor.stopFollowingUser()
Following stops automatically when you pan or zoom the canvas. Manual page changes also stop following unless the follow system itself initiated the page change.
Editor#zoomToUser animates the camera to a user's cursor position without entering follow mode. Use this for one-time navigation to a collaborator's location.
editor.zoomToUser(userId)
editor.zoomToUser(userId, { animation: { duration: 200 } })
If you're already following someone, zoomToUser() stops following first. The method switches pages if necessary, centers the camera on the user's cursor, and briefly highlights their cursor. The highlight clears after collaboratorIdleTimeoutMs (default 3 seconds). If the user is on a different page, the camera jumps without animating.
When user A follows user B who is following user C, user A follows user C directly rather than B's view of C. The editor traverses the follow chain until it finds a user who isn't following anyone else.
The traversal maintains a visited set to prevent infinite loops if users create a circular follow chain. If a cycle is detected, it returns the last valid presence before the cycle.
// A follows B follows C
// A's camera tracks C's viewport, not B's viewport
editor.startFollowingUser(userB)
// Internally resolves to C's presence if B is following C
The follow chain resolution runs on every frame, so changes in the chain (like B stopping following C) immediately affect A's target viewport without requiring A to restart following.
The DefaultFollowingIndicator component displays a colored border around the canvas when you're following someone. It reads followingUserId from instance state and fetches the corresponding presence record to get the user's color.
import { TLUserId, useEditor, usePresence, useValue } from 'tldraw'
export function DefaultFollowingIndicator() {
const editor = useEditor()
const followingUserId = useValue('follow', () => editor.getInstanceState().followingUserId, [
editor,
])
if (!followingUserId) return null
return <FollowingIndicatorInner userId={followingUserId} />
}
function FollowingIndicatorInner({ userId }: { userId: TLUserId }) {
const presence = usePresence(userId)
if (!presence) return null
return <div className="tlui-following-indicator" style={{ borderColor: presence.color }} />
}
You can override it through the FollowingIndicator slot of TLUiComponents to change its appearance or add the followed user's name. See UI components.
Check editor.getInstanceState().followingUserId to detect when a user is following someone. Use this to show UI elements like a "Stop following" button or status indicators.
const followingUserId = editor.getInstanceState().followingUserId
if (followingUserId) {
// Show stop following button
}
The presence records from Editor#getCollaborators include each user's followingUserId, so you can visualize the entire follow graph and show which users are following whom.