> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Introduction to the comment components

React components for comment threads on Sanity documents: replies, mentions, reactions, resolve, and deep links, rendering data from the App SDK.

`@sanity/collaboration` is a set of React components for commenting on Sanity content. The components are built on [Sanity UI](https://www.sanity.io/ui), and none of them fetches anything: comment data arrives as props, from the App SDK or from anywhere else.

**This is a paid feature**
This feature is available in the [Growth plan](https://www.sanity.io/pricing).

Comments are stored per organization and reached through the collaboration API.

With comments, you can:

- **Put a comments panel next to your own editing UI**, with threads, replies, and resolve.
- **Mention teammates** and have the mention resolve to a name and an avatar from your own user directory or from the SDK's.
- **Share a link that opens on one comment**, scrolls to it, and expands it out from behind collapsed replies.
- **Comment on a run of text** inside a Portable Text field, with a highlight that stays on those words as the text around them is edited.
- **Build a comment card of your own** from the same primitives the default one is assembled from, keeping the skeletons, accessible names, and action list.

## Start here

[Get started](https://www.sanity.io/docs/collaboration/get-started)
Install, wire the SDK, and render a working panel.

[Comment on Portable Text](https://www.sanity.io/docs/collaboration/comment-on-portable-text)
Inline comments in an editor, beside the panel.

[Reference](https://collaboration-platform.sanity.dev/)
Every prop, hook, helper, and type, in Storybook.

[Examples](https://github.com/sanity-io/collaboration/tree/main/sandboxes/sdk-app)
The worked examples, running against a real project.

## Requirements

> [!WARNING]
> `@sanity/collaboration` is in beta on `0.x`. [Get started](https://www.sanity.io/docs/collaboration/get-started) covers installing it.

`@sanity/collaboration` declares these peer dependencies:

| Requirement | Version |
| --- | --- |
| `react` and `react-dom` | `^19.2` |
| `@sanity/ui` | `^4.0.6` |
| `styled-components` | `^6.1` |
| `@sanity/icons` | `^5.2.1` |
| `@sanity/sdk` | `^3.11.0` |
| `@sanity/sdk-react` | `^3.11.0` |
| `@portabletext/editor` | `^8.2.4` |

`@portabletext/editor` is required rather than optional, even for a panel: the composer is a Portable Text editor. A bundler that honors `sideEffects: false` can still drop the inline comment code from an app that never renders it.

`@sanity/sdk` and `@sanity/sdk-react` are optional peers, needed only for `@sanity/collaboration/sdk`: `SDKCommentsProvider`, `SDKCommentsPanel`, `SDKPortableTextComments`, and `useSDKThreadCallbacks`. The components and hooks come from `@sanity/collaboration/ui`, and the data types and helpers come from the package root. Neither entry point imports the SDK, so a surface with no SDK, such as a test, a story, or an app with its own data layer, uses just those two.

Two more things the components assume:

- **An organization:** Comments are scoped to one, but you don't name it. The SDK uses the organization that owns the project. If the Dashboard or `organizationId` on the config names a different one, that's an error.
- **Sanity UI's stylesheet:** Sanity UI v4 ships its static styles as a plain stylesheet, so `import '@sanity/ui/styles.css'` goes wherever your app loads global CSS.

## Core concepts

Seven ideas cover the whole surface. Each one is a page, and each page links to the guides that put it to work. The reasoning behind the API shape, and what it buys you, is in [Design principles of the comment components](https://www.sanity.io/docs/collaboration/principles).

Every prop, hook, and type is in the [Storybook reference](https://collaboration-platform.sanity.dev/). The SDK hooks the components read from are covered in [Comments in the App SDK](https://www.sanity.io/docs/collaboration/sdk-overview).

### Threads and comments

A thread hangs off one field of one document and holds the comment that started it plus its replies. There is no separate thread record to create: writing the first comment on a field creates the thread, and the SDK returns the two together. A thread is the unit a card renders, and `fieldPath` is the grouping key everywhere.

A comment carries its author ID, its message as Portable Text, and the status the UI renders from. See [Threads and comments](https://www.sanity.io/docs/collaboration/threads-and-comments).

### Users and identity

Comments store user IDs, not names. Names and avatars are looked up through a synchronous `getUser` function on the provider, which makes a user directory the one thing no comments surface can do without.

`SDKCommentsProvider` fills that in from the SDK, and `CommentsProvider` takes your own answers instead. See [Users and identity](https://www.sanity.io/docs/collaboration/users-and-identity).

### Components

Five components cover a panel: `CommentList`, `CommentThread`, `Comment`, `CommentInput`, and `CommentBadge`. Every primitive the default `Comment` is assembled from is also exported, so a layout of your own is composed from finished pieces rather than rebuilt.

Actions are data: an action renders only when you passed its callback, and is disabled when the matching capability resolves to false. See [Comment components](https://www.sanity.io/docs/collaboration/components).

### Data flow

The components own no data. Reads come from the SDK's `useCommentThreads` or `useCommentsQuery`, writes from `useCommentActions`, and the wiring between them is a page of your own code.

Reads suspend on first load and writes apply optimistically, which decides what your callbacks look like: one line each, returning nothing. See [Comment data flow](https://www.sanity.io/docs/collaboration/data-flow).

### Navigation

`CommentsProvider` makes everything below it selectable and linkable. Left alone it keeps the address in state; hand it your router and a pasted link opens the panel on the comment it names. Either way the components own everything downstream of that value, including matching, scrolling, focus, and expanding collapsed replies.

What passes between you is a value and a callback rather than a router integration, so this works with React Router, Next.js, and with no router at all. See [Comment navigation and deep links](https://www.sanity.io/docs/collaboration/navigation).

### Inline comments

A comment on a run of text inside a Portable Text field is drawn as a highlight under those words. The anchor is stored as marked-up text rather than offsets, so it survives edits around it.

The components come from `@sanity/collaboration/ui` with the rest of the UI, and `@portabletext/editor` is a peer dependency alongside them. See [Inline comments](https://www.sanity.io/docs/collaboration/inline-comments).

### Levels of abstraction

The stack is two ladders you pick from independently: default components, primitives, or your own UI for what renders; and SDK hooks, SDK actions, the Sanity client, or raw HTTP for where the data comes from.

Nothing forces you to take a whole level. A panel built from the default components can read through the HTTP API, and a UI of your own can still take the SDK's optimistic writes. See [Levels of abstraction in the comment stack](https://www.sanity.io/docs/collaboration/levels-of-abstraction).

## Limitations

- **No permission-aware read of who may be mentioned.** `useUsers` returns the users on a resource, not the subset allowed to see a given document. Filter that list yourself, or mark the difference with `granted` so the picker shows an ungrantable user rather than hiding them. See [Enable mentions](https://www.sanity.io/docs/collaboration/enable-mentions).
- **Nothing reports who may comment, resolve, or delete.** No read answers it, so the provider's `capabilities` policy is where your own answer goes. The built-in defaults tie edit and delete to the comment's author and reply to an open thread, and allow resolve, react, and retry for everyone.
- **No count-only read.** A badge's number comes from reading the document's threads once and grouping them yourself. See [Show comment counts](https://www.sanity.io/docs/collaboration/show-comment-counts).
- **A failed edit rolls back silently unless you pass onError.** The local state a comment can carry covers a failed create only, so a rejected `updateComment` reverts the message with nothing on screen to say why. `useSDKThreadCallbacks` reports it to an `onError` you pass, which is where your own error goes. See [Show a failed write](https://www.sanity.io/docs/collaboration/data-flow).
- **Read-only is capabilities, not a switch.** A panel's `threadProps` capabilities reach only its cards. An inline editor beside it can still start threads, unless the provider's policy turns them off too.
- **Retrying a create is not guaranteed idempotent.** Retry re-issues `createComment` with the same comment ID. If the first request reached the server, the API can reject the retry as a conflict.
- **One string cannot be translated.** The provider's `labels` reaches every other string the components render, but the delete dialog's close button keeps Sanity UI's own `Close dialog`. See [Translate the UI text](https://www.sanity.io/docs/collaboration/translate-the-ui).

