Document-level comments, multi-document panels and DOM styling props

Published: October 29, 2026

v0.2.0

This release lets a comment belong to a whole document, groups comments from several documents in one list or panel, and replaces Sanity UI layout props with plain DOM attributes. It also renames the SDK components after their @sanity/collaboration/ui counterparts. Several of these changes break CommentsPanel and styling code written for 0.1.0.

New features

  • Document-level comments: a thread can be on a document as a whole, not only on one of its fields, with fieldPath: ''. compose={{fieldPath: ''}} starts one from CommentsPanel, whose composer then prompts "Comment on the document…". CommentList heads these threads with the document when it names documents, and otherwise with the new labels.list.document (default "Document"). The Sanity Studio comments inspector doesn't list document-level comments. Document-level comments need @sanity/sdk and @sanity/sdk-react 3.11.0 or later.
  • Multi-document lists: CommentList and CommentsPanel group comments from several documents. Groups are one field of one document, keyed on the new optional CommentData.documentId (and documentType). Once the threads span more than one document, each heading names the document as well as the field. documentTitle(group) names a group's document, renderFieldHeading and renderFieldAction receive the group's documentId, and scrollToField takes a CommentTarget as well as a field path.
  • Always-visible actions: Comment and CommentThread take actions: 'hover' | 'always'. 'always' keeps the action row in view instead of revealing it on hover or focus. The default is 'hover'.
  • SDKCommentsPanel takes onCommentCreated(comment), called once a new thread is created, as on SDKPortableTextComments. Use it to close a composer opened with compose or to select the new thread.
  • useSDKThreadCallbacks also accepts a function from a thread to its document's handle.

Improvements

  • In development, CommentsPanel warns when the threads span several documents and compose has no documentId.
  • panel.composerPlaceholder receives the full target, so you can name the document as well as the field.
  • Multi-document headings truncate the document title before the field, and show the full heading in a tooltip when needed.
  • The inline comment button opens from the bottom-right corner of the selection and runs right, or left when it doesn't fit, instead of centered under the selection. The placement prop on InlineCommentInput still overrides it.

Breaking changes

  • SDK component names: DocumentCommentsPanel is now SDKCommentsPanel (with SDKCommentsPanelProps and SDKCommentsPanelAction), and InlineCommentsPlugin is now SDKPortableTextComments (with SDKPortableTextCommentsProps). Props and behavior are unchanged, so update the imports.
  • Styling props: Comment, CommentThread, CommentList and CommentBadge no longer accept Sanity UI props (padding, radius, tone, border, shadow, gap, mode, fontSize and others). Each takes the DOM attributes of the element it renders: className, style, id, aria-* and event handlers. Style them through the Sanity UI theme, and use className or style for placement. The data-* attributes in the rendered DOM aren't a styling API and can change in any release. See Theme the components.
  • onThreadCreate(fieldPath, message) is now onThreadCreate(target, message), and labels.composerPlaceholder(fieldPath) is now composerPlaceholder(target). target is the new CommentTarget, {fieldPath, documentId?}, exported from the root entry.
  • draftKey is removed. Drafts are always kept per target, so they survive a composer unmounting and carry between panels over the same document. The new optional draftScope string prefixes their keys, to keep two panels over one document apart.
  • The fieldPath prop on CommentsPanel is replaced by compose: {fieldPath, documentId?, onDiscard?}. documentId says which document the composer writes to, and onDiscard makes it a composer opened on request: it takes focus when it mounts, and Cancel or Escape calls onDiscard. SDKCommentsPanel takes compose without documentId, since its document is the handle's.
  • renderFieldHeading(group) is now renderFieldHeading(group, {heading}) on both CommentList and CommentsPanel, where heading is the built-in heading. Both components now read the result the same way: null renders no heading and undefined renders the built-in one.
  • The composer above the list remounts when its target changes, so unsent text stays with the field and document it was written for.
Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change site theme