tldraw offline is the local whiteboard for you and your agents.Give it a try

BlogProduct

A comment is not a shape

Jessica Edwards

Canvases are naturally collaborative spaces. You sketch a flow, drop in some wireframes, rough out a plan, but the more you build together, the more likely it is that someone will want to point something and say ‘hey, what about this?’

For a long time, the only way to do that in tldraw was to fake it. You could keep it on the canvas — type a note with the text tool, or draw a box around the thing and write next to it. But what if you want to tag someone, or reply to a comment, or mark it as done?

You also ran the risk of your private thoughts and processes getting exported with the final design.

You could build a comment system of your own, off to the side — which fixes all that, but now your comments live disconnected from the canvas, with no idea what they're pointing at or where it went when the board moved. It’s also pretty time-consuming to do.

So we built commenting properly, as an SDK package: @tldraw/commenting.

Comments on

To use them, you just pick the comment tool and click anywhere. A pin drops where you clicked and a thread opens next to it. You can react to a comment with an emoji, @mention a teammate, and resolve the thread when it's handled.

Check out our release notes blog for an overview.

The pin stays put on the canvas, attached to whatever you were pointing at.

‘A comment points at something’ might feel like a self-evident decision, until you’re the one having to figure out how it’ll actually work.

The decision that shapes everything else

A comment thread is a record. It carries a pointer to its target and a note saying what kind of target that is. The drawing doesn't know the comments exist. That way, a comment is its own record and can carry its own rules, including that only its author can edit it, instead of inheriting the "anyone can move anything" freedom that shapes have.

So what does a comment point at?

Once a comment is a separate record, you have to answer a different question: where does it live?

The easy answer is to store the pin’s X/Y coordinate, but that’s fool’s gold. The moment someone resizes the shape you were pointing at, your pin is floating in the wrong place.

The trick is to store the position as a fraction of the shape rather than a fixed point: "70% across, 20% down." Resize the shape and the pin stays at 70% across, so it moves with the thing it's describing. What we’re actually storing a normalized anchor, which is a fancy way of saying a position relative to the shape rather than a spot on the canvas.

A comment can anchor four ways: to a point on the page, to a shape it then follows, to a region you draw around several shapes, or to the whole page. (We prototyped a fifth — anchoring to a highlighted range of text — and cut it before shipping. Worth its own post.)

What happens when you delete the thing?

But what happens when someone deletes the shape the comment was pointing to? You might still want the conversation, but not the element it happened to be attached to.

Alternatively, you could keep the comment around without any canvas location — it survives, but floating, detached from where the conversation happened.

We decided that it should convert to a point — it drops an anchor at exactly the spot the pin was sitting and stays there, as a comment about that place on the canvas. The conversation outlives its subject. And if you undo the delete, the shape comes back and the pin re-attaches to it — unless you'd deliberately moved the pin in the meantime, in which case your placement wins.

Two ways to use it

The package is deliberately built in two layers, because two very different developers want it.

If you just want comments on your canvas, there's a batteries-included overlay — <CanvasComments>. You render it in front of the editor, and it does everything above: draws the pins, opens the threads, handles the clustering, reads and writes the records itself.

If you want your own commenting UI — a review panel, a custom thread design, comments that live somewhere other than the canvas — everything the overlay is built from is exported too.

Reactive hooks that hand you the live comment data (useComments, useCommentThreads), and a kit of presentational pieces (CommentComposer, CommentThread, CommentPin, and so on) that don't depend on the editor at all. You can replace any single part of the overlay, or assemble a completely different surface from the pieces.

That split — take the whole thing, or drop down to the parts — is the real API decision. And because the comments are just records pointing at a document, you can pull them out of the canvas entirely and use them elsewhere in your app.

What’s next

The lesson underneath all of this is a distinction we didn't use to draw: between things that live in the canvas and things that live alongside it. A comment is neither part of your drawing nor floating in some unrelated system — it sits parallel to the canvas and points in.

We ended up building that separation twice. In our sync engine, we introduced a new concept: objects — data that travels with a document but lives parallel to the canvas rather than inside it, which is the lane comment records ride on. The commenting package splits the same way in the UI: a presentational kit that knows nothing about the editor, plus a thin canvas layer that wires it to the board.

Comments are the first thing we've built this way. They won't be the last.

Trusted by these companies

  • bigpi
  • CADChat
  • Google
  • Replit
  • Shopify
  • BlackRock
  • ClickUp
  • Autodesk
  • Google Stitch
  • Luma
  • Runway
  • SchoolAI
  • Honeycomb
  • Padlet
  • Genio
  • Pollination
  • JAM
  • Craft
  • Legendkeeper
  • Mobbin
  • Matilda workspace
  • Brisk
  • Aries
  • AlAI
  • Dirac