Commenting on mobile
Commenting with the mobile layout, where the thread popover and composer place themselves to stay on-screen above the keyboard.
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. With `enableRegions`, dragging the comment tool out draws a
// rectangle and anchors the comment to that area instead of a point or shape.
const COMMENT_TOOLS = [CommentTool.configure({ enableRegions: true })]
export default function CommentingMobileExample() {
// 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
// `forceMobile` pins the mobile breakpoint on any screen size, so the mobile thread and
// composer placement is visible here on desktop. On a real device the same layout comes
// from the narrow viewport — the thread popover and composer position themselves against
// the visual viewport, riding up rather than hiding behind the software keyboard.
forceMobile
// 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>
)
}
The same setup as the Commenting example, with forceMobile on the editor so the mobile layout shows on any screen size.
In mobile mode the thread popover and the new-comment composer position themselves relative to the pin and the visual viewport rather than sitting at a fixed offset. They pick a side of the pin that keeps the whole panel on-screen, and ride up when the software keyboard shrinks the viewport instead of hiding behind it. Place a comment near an edge to see the placement adapt.
Drop forceMobile and the layout switches on the viewport width, the way it does on a real device.
Is this page helpful?
Prev
Multiplayer sync with private contentNext
Manually update user presence