Commenting
Add comments to the canvas with the commenting toolkit.
import {
CanvasComments,
CommentAuthor,
CommentTool,
commentToolOverrides,
filterMentionMembers,
MentionMember,
} from '@tldraw/commenting'
import { getLicenseKey } from '@tldraw/dotcom-shared'
import { useMemo } from 'react'
import { commentSchemaRecords, createTLSchema, createTLStore, TLComponents, Tldraw } from 'tldraw'
import '@tldraw/commenting/commenting.css'
import 'tldraw/tldraw.css'
// A demo avatar image (inline SVG) so one author shows an image instead of a colored initial.
const ADA_AVATAR =
'data:image/svg+xml,' +
encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><rect width="28" height="28" fill="#0E9F6E"/><circle cx="14" cy="11" r="5" fill="#fff"/><ellipse cx="14" cy="24" rx="9" ry="7" fill="#fff"/></svg>`
)
// The people who can be @-mentioned. A real app would pull this from its own roster; the composer
// filters this list as you type after `@`. Ids match the author directory below.
const MEMBERS: MentionMember[] = [
{ id: 'me', name: 'You', color: '#EC5E41', you: true },
{ id: 'ada', name: 'Ada Lovelace', color: '#0E9F6E', image: ADA_AVATAR },
{ id: 'grace', name: 'Grace Hopper', color: '#4465E9' },
{ id: 'alan', name: 'Alan Turing', color: '#9C1FBE' },
]
// A tiny local user directory so the flow shows names, colors, and images instead of ids. A real
// app would resolve these from its own identity system.
const AUTHORS: Record<string, CommentAuthor> = Object.fromEntries(MEMBERS.map((m) => [m.id, m]))
const resolveAuthor = (id: string): CommentAuthor => AUTHORS[id] ?? { name: id }
// Region comments are off by default (click-only). Configuring the tool with `enableRegions` lets
// dragging the comment tool out create a comment anchored to a rectangular area.
const COMMENT_TOOLS = [CommentTool.configure({ enableRegions: true })]
export default function CommentingExample() {
// Comments are stored as `comment-thread` and `comment` records in the editor's own store.
// Registering `commentSchemaRecords` on the schema is all it takes to persist and sync them
// alongside shapes — no separate backend, so the whole flow runs in-memory here.
const store = useMemo(
() => createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
[]
)
// `CanvasComments` reads those records reactively and draws the pins, threads, and composer.
// Mounting it in front of the canvas is the entire UI layer.
const components = useMemo<TLComponents>(
() => ({
InFrontOfTheCanvas: () => (
<CanvasComments
currentUserId="me"
resolveAuthor={resolveAuthor}
getMentionSuggestions={(query) => filterMentionMembers(MEMBERS, query)}
/>
),
}),
[]
)
return (
<div className="tldraw__editor">
<Tldraw
// Commenting is a licensed feature. Every feature is enabled in local development, but a
// deployed app needs a license key that includes commenting — swap in your own key here.
licenseKey={getLicenseKey()}
store={store}
tools={COMMENT_TOOLS}
overrides={[commentToolOverrides]}
components={components}
/>
</div>
)
}
Comment threads are records in the editor's store, so they persist and sync exactly like shapes. Register commentSchemaRecords on the schema, then render CanvasComments in front of the canvas.
Press c or pick the comment tool, then click anywhere to start a thread, or click a shape to attach one to it. Type @ in a composer to mention someone.
Is this page helpful?
Prev
Popup shapeNext
Multiplayer sync