Multiplayer sync with custom people menu
This example demonstrates how to build a custom people menu (or facepile) that shows information about all users in a multiplayer session.
This is useful when you want to create a more detailed or custom-styled presence indicator than the default tldraw provides. This example shows user's names, ids, colors, and cursor postition, all information provided in the TLInstancePresence
returned by editor.getCollaborators()
.
import { useSyncDemo } from '@tldraw/sync'
import { Tldraw, useEditor, useValue } from 'tldraw'
import 'tldraw/tldraw.css'
import './sync-custom-people-menu.css'
// [1]
const components = {
SharePanel: () => (
<div className="tlui-share-zone" draggable={false}>
<CustomPeopleMenu />
</div>
),
}
// [2]
export default function SyncCustomPeopleMenuExample({ roomId }: { roomId: string }) {
const store = useSyncDemo({ roomId })
return (
<div className="tldraw__editor">
<Tldraw store={store} deepLinks components={components} />
</div>
)
}
// [3]
function CustomPeopleMenu() {
const editor = useEditor()
// [a]
const myUserColor = useValue('user', () => editor.user.getColor(), [editor])
const myUserName = useValue('user', () => editor.user.getName() || 'Guest', [editor])
const myUserId = useValue('user', () => editor.user.getId(), [editor])
// [b]
const allOtherPresences = useValue('presences', () => editor.getCollaborators(), [editor])
return (
<div className="custom-people-menu">
{/* [c] */}
<div className="user-section">
<h4 className="section-title">Me</h4>
<div className="user-info">
<div className="user-avatar" style={{ background: myUserColor }} />
<span className="user-name" style={{ color: myUserColor }}>
{myUserName}, ID: {myUserId}
</span>
</div>
</div>
{/* [d] */}
{allOtherPresences.length > 0 && (
<div className="other-users-section">
<h4 className="section-title">Other connected users:</h4>
<div className="other-users-list">
{allOtherPresences.map(({ userId, userName, color, cursor }) => (
<div key={userId} className="other-user-item">
<div className="other-user-avatar" style={{ background: color }} />
<span className="other-user-name" style={{ color: color }}>
{userName || `ID: ${userId}`}
</span>
<span className="cursor-info">
Cursor
<br />
{cursor && Number.isFinite(cursor.x) && Number.isFinite(cursor.y)
? `(${Math.round(cursor.x)}, ${Math.round(cursor.y)})`
: 'cursor data unavailable'}
</span>
</div>
))}
</div>
</div>
)}
</div>
)
}
/*
[1]
We define custom components to override tldraw's default UI. Here we're replacing the SharePanel with our own CustomPeopleMenu component.
[2]
This is the main component that sets up a synced tldraw editor. It uses the useSyncDemo hook to create a multiplayer store and passes our custom components to replace the default UI elements.
[3]
The CustomPeopleMenu component displays information about all connected users. It uses tldraw's collaboration hooks to access real-time presence data. You can do whatever you like in here, see the TLInstancePresence interface to see what informatino you have access to.
[a]
We use the useValue hook to reactively get the current user's information (color, name, and ID). These values will automatically update if the user changes their name or the system assigns a new color (note: the examlpe doesn't allow for name changing).
[b]
We get the live presence of all other users information using the editor's getCollaborators() method. We need to call getCollaborators() in a useValue hook in order for the presence info to be reactive.
[c]
Display the current user's information with their color indicator and name. We show both the display name and the internal user ID for debugging purposes.
[d]
For each connected collaborator, we display their name (or ID if no name is set), their color indicator, and their current cursor position.
*/
Is this page helpful?
Prev
Multiplayer sync with a custom shapeNext
Multiplayer sync with custom presence