Commenting on mobile
Commenting on a mobile layout: 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 (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 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 commenting toolkit 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