# Adding comments and replies in our JavaScript PDF viewer

Nutrient Web SDK provides a user interface (UI) for viewing, adding, and deleting comments in PDF documents. Comments enable collaborative workflows where multiple users can discuss specific sections of a PDF document without leaving the viewer.

If you’re using Nutrient Web SDK with [Document Engine](https://www.nutrient.io/guides/document-engine.md) and have Nutrient Instant enabled, comments are available starting from version [2020.1](https://www.nutrient.io/guides/web/changelog.md#2020.1). For more information, refer to the [Nutrient Instant](https://www.nutrient.io/guides/web/instant-synchronization.md) guide.

If you’re not using Document Engine, comments are available starting from version [2023.3](https://www.nutrient.io/guides/web/changelog.md#2023.3.0).

The main difference between the two setups is how collaboration works. With Document Engine, users see each other’s changes in real time. Without it, collaboration is asynchronous — changes appear only after a document is reloaded.

## Licensing

Comments require a separate component in your Nutrient license. Without this included in your license, you won’t be able to add the functionality of viewing, searching, or adding comments in your application. [Contact our Sales team](https://www.nutrient.io/contact-sales/) to add comments to your license.

If you’re a new customer, you can [try comments](https://www.nutrient.io/demo/instant-collaboration/) without a license key. If you’re an existing customer, ask our [Sales team](https://www.nutrient.io/contact-sales/) for a trial license if you’re interested.

## Terminology

Before starting, here are a few key terms related to comments:

- **Root annotation** — The annotation to which all the comments in a single thread are linked.

- **Comment thread** — A group of comments associated with the same root annotation.

- **Comment** — A single comment added by a user.

### Root annotation types

All comments are linked to their respective root annotations. The comments with the same root annotation are part of a single comment thread. There can be two types of root annotations:

- [`MarkupAnnotation`](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.MarkupAnnotation.html) — You can start a new comment thread by selecting some text and clicking  in the markup annotation inline toolbar. In this case, the markup annotation acts as the root annotation.

- [`CommentMarkerAnnotation`](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.CommentMarkerAnnotation.html) — A comment marker annotation is a new annotation that can be added anywhere in a PDF document and used to start comment threads.

## Getting started

By default, we don’t show the comment tool  in the main toolbar. This is because we want you to think about the workflow you want for your users and then decide whether or not you want to add it in the main toolbar. For example, if you want to enable the creation of comments from the main toolbar and disable sticky notes, use the following code snippet:

```javascript

const toolbarItems = NutrientViewer.defaultToolbarItems.concat({ type: "comment" }) // Add comment tool..filter((item) => item.type!== "note"); // Remove note tool.

NutrientViewer.load({
  //...
  toolbarItems,
});

```

You’ll have to add the comment tool in the main toolbar if you want to add comments using the comment marker annotation.

## Adding a comment

You can add comments in two ways, depending on the type of root annotation.

### From the main toolbar

This method involves the creation of `CommentMarkerAnnotation` before the creation of comments. To add a comment marker annotation, click the comment tool  in the main toolbar and choose a location on the page where you want to add the comment. A comment editor will appear where you can write your first comment and start a new thread.![Comment main toolbar](https://www.nutrient.io/@/assets/guides/web/comments/introduction-to-instant-comments/comment-main-toolbar.png)

### Using markup annotations

To add a comment linked to a text annotation, create a new markup annotation and click the comment tool  in the inline toolbar. This opens a comment editor where you can write your first comment and begin a new thread.![Comment inline toolbar](https://www.nutrient.io/@/assets/guides/web/comments/introduction-to-instant-comments/comment-inline-toolbar.png)

## Mentioning users in a comment

To mention a user in a comment, type @, followed by the user’s name, and select the user from the list.

Mentions are only supported in Nutrient Web SDK, and not on iOS or Android.

### Setting the list of mentionable users

To specify the users who can be mentioned in comments, follow the steps below.

1. Create a `MentionableUser` object for each mentionable user with the following string type properties:
   - Required: `name`, `id`, `displayName`
   - Recommended: `description`
   - Optional: `avatar`

2. Create a list of the `MentionableUser` objects.

3. Pass the list to the `mentionableUsers` configuration property when you load the Nutrient Web SDK viewer, or to the `setMentionableUsers` method after loading the viewer.

The example below sets two mentionable users when loading the Nutrient Web SDK viewer:

```js

NutrientViewer.load({
  //... Other configuration options.
  mentionableUsers: [
    {
      name: "Jane Doe",
      displayName: "Jane Doe",
      id: "jane_doe",
      description: "jane@doe.com",
    },
    {
      name: "John Doe",
      displayName: "John Doe",
      id: "john_doe",
      description: "john@doe.com",
    },
  ],
});

```

The example below changes the list of mentionable users after the Nutrient Web SDK viewer has loaded:

```js

instance.setMentionableUsers([
  {
    name: "Jane Doe",
    displayName: "Jane Doe",
    id: "jane_doe",
    description: "jane@doe.com",
  },
  {
    name: "John Doe",
    displayName: "John Doe",
    id: "john_doe",
    description: "john@doe.com",
  },
]);

```

### Getting the list of users mentioned in a comment

To get the list of all users mentioned in a comment, call the `getMentionedUserIds` method on the comment object:

```js

comment.getMentionedUserIds();

```

### Notifying mentioned users

To notify users who are mentioned in a comment, add event listeners to the loaded instance.

One approach is to listen to the changes made by a specific user. To do this, listen to the `comments.mention` event and send notifications when the event is triggered. In the example below, the listener is triggered when the user `john_doe` adds or removes the `someCommentObject` comment. The listener won’t trigger if the changes are made by another user, even if those changes are visible to you:

```js

instance.addEventListener("comments.mention", args: {
    comment: someCommentObject,
    modifications: [{
        userId: "john_doe",
        action: "ADDED" | "REMOVED"
        }]
    } => void)

```

Another approach is to listen to all changes to comments regardless of who made them. Listen to the `comments.create`, `comments.update`, and `comments.delete` events to send notifications when comments are created, updated, or deleted. The example below adds separate event listeners for each of these three cases and determines the users mentioned in the affected comments. If you implement a way to keep track of users mentioned in different comments, you can determine if new user mentions have been added or deleted and send notifications to the affected users.

This approach gives you control over the different ways comments can change (creation, update, deletion). The limitation is that changes by any user trigger notifications; you can’t specify which users’ changes trigger them. You also can’t determine who made the last change to a comment, but you can use the `comment.creatorName` property to identify who created it:

```js

instance.addEventListener("comments.create", (createdComments) => {
  const users = createdComments.get(0).forEach((comment) => comment.getMentionedUserIds());
});

instance.addEventListener("comments.update", (updatedComments) => {
  const users = updatedComments.get(0).forEach((comment) => comment.getMentionedUserIds());
});

instance.addEventListener("comments.delete", (deletedComments) => {
  const users = deletedComments.get(0).forEach((comment) => comment.getMentionedUserIds());
});

```

## Deleting a comment

You can delete an individual comment by clicking the  delete button. If all the comments of a thread are deleted, the corresponding root annotation is automatically deleted.

Document Engine’s REST API currently doesn’t expose an endpoint for deleting a comment. In Nutrient Web SDK, you can delete comments programmatically using [`instance.delete()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#delete).

## Working with comments programmatically

While the UI provides tools for users to add and manage comments interactively, you can also create, read, update, and delete (CRUD) comments programmatically using the Nutrient Web SDK API. Use the programmatic API when you need to:

- Automate comment workflows

- Import comments from external systems

- Build custom commenting interfaces

- Prepopulate documents with review comments

### Basic workflow

Programmatic comment CRUD uses the same change APIs as annotations and bookmarks: [`instance.create()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#create), [`instance.update()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#update), and [`instance.delete()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#delete).

A comment thread has two parts:

1. A root annotation — The annotation that anchors the thread. This can be a [`CommentMarkerAnnotation`](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.CommentMarkerAnnotation.html) placed anywhere on the page, or an existing markup annotation such as a highlight.

2. A [`Comment`](https://www.nutrient.io/api/web/classes/NutrientViewer.Comment.html) — The text content in the thread.

The `rootId` property of each `Comment` must match the `id` of the root annotation. The comment’s own `id` is separate; if you don’t provide one, Nutrient Web SDK assigns it when the comment is created.

Comments are immutable data structures. To modify a comment, use `.set()` to create a new instance with updated properties, similar to how [`ViewState`](https://www.nutrient.io/guides/web/customizing-the-interface/viewstate/) works.

### Complete CRUD example

The following example creates a comment marker thread, reads the created comment, updates its text, and shows how to delete it. You can also [try the complete flow in Playground](https://www.nutrient.io/demo/sandbox/?p=eyJ2IjoxLCJqcyI6Ik51dHJpZW50Vmlld2VyLmxvYWQoe1xuICAuLi5iYXNlT3B0aW9ucyxcbiAgdGhlbWU6IE51dHJpZW50Vmlld2VyLlRoZW1lLkRBUkssXG59KS50aGVuKGFzeW5jIChpbnN0YW5jZSkgPT4ge1xuICBjb25zdCBpZCA9IE51dHJpZW50Vmlld2VyLmdlbmVyYXRlSW5zdGFudElkKCk7XG5cbiAgLy8gMS4gQ1JFQVRFOiBTdGFydCBhIGNvbW1lbnQgdGhyZWFkXG4gIGNvbnN0IG1hcmtlckFubm90YXRpb24gPVxuICAgIG5ldyBOdXRyaWVudFZpZXdlci5Bbm5vdGF0aW9ucy5Db21tZW50TWFya2VyQW5ub3RhdGlvbih7XG4gICAgICBpZCxcbiAgICAgIHBhZ2VJbmRleDogMCxcbiAgICAgIGJvdW5kaW5nQm94OiBuZXcgTnV0cmllbnRWaWV3ZXIuR2VvbWV0cnkuUmVjdCh7XG4gICAgICAgIHRvcDogNTAsXG4gICAgICAgIGxlZnQ6IDUwLFxuICAgICAgICB3aWR0aDogMjAsXG4gICAgICAgIGhlaWdodDogMjAsXG4gICAgICB9KSxcbiAgICB9KTtcblxuICBjb25zdCBjb21tZW50ID0gbmV3IE51dHJpZW50Vmlld2VyLkNvbW1lbnQoe1xuICAgIHBhZ2VJbmRleDogMCxcbiAgICB0ZXh0OiB7XG4gICAgICBmb3JtYXQ6IFwicGxhaW5cIixcbiAgICAgIHZhbHVlOiBcIkhlbGxvIHdvcmxkIGNvbW1lbnRcIixcbiAgICB9LFxuICAgIHJvb3RJZDogaWQsXG4gIH0pO1xuXG4gIGF3YWl0IGluc3RhbmNlLmNyZWF0ZShbbWFya2VyQW5ub3RhdGlvbiwgY29tbWVudF0pO1xuXG4gIC8vIDIuIFJFQUQ6IFJldHJpZXZlIGFsbCBjb21tZW50c1xuICBsZXQgY29tbWVudHMgPSBhd2FpdCBpbnN0YW5jZS5nZXRDb21tZW50cygpO1xuICBjb25zdCBjb21tZW50VG9FZGl0ID0gY29tbWVudHMuZmluZCgoY29tbWVudCkgPT5cbiAgICBjb21tZW50LnRleHQudmFsdWUuaW5jbHVkZXMoXCJIZWxsbyB3b3JsZFwiKVxuICApO1xuXG4gIC8vIDMuIFVQREFURTogTW9kaWZ5IHRoZSBjb21tZW50XG4gIGNvbnN0IHVwZGF0ZWRDb21tZW50ID0gY29tbWVudFRvRWRpdC5zZXQoXCJ0ZXh0XCIsIHtcbiAgICBmb3JtYXQ6IFwicGxhaW5cIixcbiAgICB2YWx1ZTogXCJIaSB0aGVyZVwiLFxuICB9KTtcbiAgYXdhaXQgaW5zdGFuY2UudXBkYXRlKHVwZGF0ZWRDb21tZW50KTtcblxuICAvLyA0LiBERUxFVEU6IFJlbW92ZSB0aGUgY29tbWVudCAob3B0aW9uYWwpXG4gIC8vIGF3YWl0IGluc3RhbmNlLmRlbGV0ZShjb21tZW50VG9FZGl0LmlkKTtcbn0pOyIsImNzcyI6Ii8qIEFkZCB5b3VyIENTUyBoZXJlICovXG5cdCIsInNldHRpbmdzIjp7ImZpbGVOYW1lIjoiYmFzaWMucGRmIn19):

```javascript

NutrientViewer.load({...baseOptions,
  theme: NutrientViewer.Theme.DARK,
}).then(async (instance) => {
  const id = NutrientViewer.generateInstantId();

  // 1. CREATE: Start a comment thread.
  const markerAnnotation =
    new NutrientViewer.Annotations.CommentMarkerAnnotation({
      id,
      pageIndex: 0,
      boundingBox: new NutrientViewer.Geometry.Rect({
        top: 50,
        left: 50,
        width: 20,
        height: 20,
      }),
    });

  const firstComment = new NutrientViewer.Comment({
    pageIndex: 0,
    text: {
      format: "plain",
      value: "Hello world comment",
    },
    rootId: id,
  });

  await instance.create([markerAnnotation, firstComment]);

  // 2. READ: Retrieve all comments.
  const comments = await instance.getComments();
  const commentToEdit = comments.find((item) => item.rootId === id);

  if (!commentToEdit) {
    return;
  }

  // 3. UPDATE: Modify the comment.
  const updatedComment = commentToEdit.set("text", {
    format: commentToEdit.text.format,
    value: "Hi there",
  });
  await instance.update(updatedComment);

  // 4. DELETE: Remove the comment (optional).
  // await instance.delete(updatedComment);
});

```

### Creating comments

To create a comment thread with a marker annotation, create both the marker and the first comment in the same [`instance.create()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#create) call:

```javascript

const rootId = NutrientViewer.generateInstantId();

// Create the marker that anchors the thread on the page.
const marker = new NutrientViewer.Annotations.CommentMarkerAnnotation({
  id: rootId,
  pageIndex: 0,
  boundingBox: new NutrientViewer.Geometry.Rect({
    top: 50,
    left: 50,
    width: 20,
    height: 20,
  }),
});

// Create the first comment in the thread.
const comment = new NutrientViewer.Comment({
  pageIndex: 0,
  text: { format: "plain", value: "Your comment text" },
  rootId, // Links the comment to the marker annotation.
});

// Add both to the document.
const createdChanges = await instance.create([marker, comment]);

```

The returned `createdChanges` value can be passed to [`instance.ensureChangesSaved()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#ensurechangessaved) if your integration needs to wait until the new thread has been persisted.

**Key points:**

- Use [`NutrientViewer.generateInstantId()`](https://www.nutrient.io/api/web/functions/NutrientViewer.generateInstantId.html) to create a unique ID.

- The marker annotation’s `id` and the comment’s `rootId` must use the same value.

- The comment’s own `id` is independent from `rootId`; Nutrient Web SDK can generate it for you.

- Use [`instance.create()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#create) with an array containing both objects.

To start a comment thread on an existing annotation, mark that annotation as a comment thread root and create a comment whose `rootId` matches the annotation’s `id`:

```javascript

const highlightId = "01J..."; // ID of an existing highlight annotation.
const pageIndex = 0; // Page containing the highlight annotation.
const annotations = await instance.getAnnotations(pageIndex);
const highlight = annotations.find((annotation) => annotation.id === highlightId);

if (highlight) {
  const rootAnnotation = highlight.set("isCommentThreadRoot", true);
  const comment = new NutrientViewer.Comment({
    pageIndex: highlight.pageIndex,
    rootId: highlight.id,
    text: { format: "plain", value: "Please review this highlight." },
  });

  await instance.update(rootAnnotation);
  await instance.create(comment);
}

```

### Reading comments

Retrieve comments using [`instance.getComments()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#getcomments):

```javascript

const allComments = await instance.getComments();
console.log(`Total comments: ${allComments.size}`);

// Find all comments in a thread.
const rootId = "01J..."; // ID of the root annotation for the thread.
const threadComments = allComments.filter((comment) => comment.rootId === rootId);

// Find comments by text content.
const searchResults = allComments.filter((c) =>
  (c.text.value?? "").includes("search term"),
);

// Include draft comments created by the SDK while a new thread is being edited.
const commentsIncludingDrafts = await instance.getComments({
  includeDrafts: true,
});

```

By default, `getComments()` filters out draft comments. Use `includeDrafts: true` when building a custom UI that needs access to SDK-created draft comments. The text currently being typed in an open editor is managed by the editor until it’s saved.

### Updating comments

Modify existing comments using [`instance.update()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#update):

```javascript

const comments = await instance.getComments();
const target = comments.find((c) =>
  (c.text.value?? "").includes("Hello world"),
);

if (target) {
  // Update single or multiple properties using `.set()`.
  const updated = target.set("text", { format: target.text.format, value: "Updated text" }).set("creatorName", "Updated Author");

  await instance.update(updated);
}

```

Keep the comment’s `rootId` pointing to the same root annotation. If you need to move the visible marker for a marker-based thread, update the associated [`CommentMarkerAnnotation`](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.CommentMarkerAnnotation.html) instead.

### Deleting comments

Remove comments programmatically using [`instance.delete()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#delete):

```javascript

const rootId = "01J..."; // ID of the root annotation for the thread.
const allComments = await instance.getComments();
const threadComments = allComments.filter((comment) => comment.rootId === rootId);
const firstThreadComment = threadComments.first();

// Delete one comment from the thread.
if (firstThreadComment) {
  await instance.delete(firstThreadComment);
}

// Or delete the root annotation, which removes the whole thread.
// await instance.delete(rootId);

// Or delete multiple comments at once.
// await instance.delete(threadComments);

```

Comments are automatically removed when their root annotation is deleted. If you use [`instance.history.undo()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#history) to restore a deleted root annotation, its associated comments will also be restored.

### Saving comment changes

New comments, updates, and deletions are visible in the UI immediately. Whether they’re persisted automatically depends on your [`Configuration#autoSaveMode`](https://www.nutrient.io/api/web/NutrientViewer.Configuration.html#autoSaveMode) setting and the selected [`AutoSaveMode`](https://www.nutrient.io/api/web/enums/NutrientViewer.AutoSaveMode.html).

If you use manual saving, check [`instance.hasUnsavedChanges()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#hasunsavedchanges) and call [`instance.save()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#save) to persist all pending changes:

```javascript

if (instance.hasUnsavedChanges()) {
  await instance.save();
}

```

If you need to wait until specific comments or marker annotations are persisted, use [`instance.ensureChangesSaved()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#ensurechangessaved) with the changes returned by `create`, `update`, or `delete`:

```javascript

const createdChanges = await instance.create([marker, comment]);
await instance.ensureChangesSaved(createdChanges);

```

### Listening for comment changes

Use [`comments.change`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_change) when your UI needs to refresh whenever comments change. Use the more specific [`comments.create`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_create), [`comments.update`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_update), and [`comments.delete`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_delete) events when you need to react to a specific operation:

```javascript

instance.addEventListener("comments.change", async () => {
  const latestComments = await instance.getComments();
  // Update your own comments UI.
});

instance.addEventListener("comments.create", (createdComments) => {
  createdComments.forEach((comment) => {
    console.log(`Created comment: ${comment.id}`);
  });
});

instance.addEventListener("comments.update", (updatedComments) => {
  // React to updated comments.
});

instance.addEventListener("comments.delete", (deletedComments) => {
  // React to deleted comments.
});

```

For manual save workflows, [`comments.willSave`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_will_save) fires before comment changes are saved, and [`comments.didSave`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_did_save) fires after saving finishes.

### Limitations

The programmatic API works with comment records. It doesn’t directly modify text currently being typed in the built-in comment editor because the editor manages that local state until the user saves the comment.

If you need to set initial comment text programmatically, use [`setOnCommentCreationStart()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#setoncommentcreationstart):

```javascript

instance.setOnCommentCreationStart((comment) => {
  return comment.set("text", {
    format: "plain",
    value: "Pre-populated text",
  });
});

```

For use cases requiring live draft editing — such as accessibility tools like dictation or text prediction — contact our [Support team](https://support.nutrient.io/hc/en-us/requests/new) to discuss your requirements.

### API reference

For detailed API documentation, see:

- [`Comment` class](https://www.nutrient.io/api/web/classes/NutrientViewer.Comment.html) — Comment object properties and methods

- [`instance.create()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#create) — Create comments programmatically

- [`instance.getComments()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#getcomments) — Retrieve all comments

- [`instance.update()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#update) — Update saved comments

- [`instance.delete()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#delete) — Delete comments

- [`instance.save()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#save) — Persist pending changes manually

- [`instance.ensureChangesSaved()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#ensurechangessaved) — Wait for specific changes to be persisted

- [`comments.change`](https://www.nutrient.io/api/web/enums/NutrientViewer.EventName.html#comments_change) — Listen for comment changes

- [`CommentMarkerAnnotation`](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.CommentMarkerAnnotation.html) — Visual marker for comment threads

## Disabling the comments UI

If your license includes comments but you want to disable letting the user view and add comments, you can set [`showComments`](https://www.nutrient.io/api/web/classes/NutrientViewer.ViewState.html#showcomments) to `false` in [`ViewState`](https://www.nutrient.io/api/web/classes/NutrientViewer.ViewState.html):

```js

const initialViewState = new NutrientViewer.ViewState({
  showComments: false,
});

const instance = NutrientViewer.load({
  //... other options
  initialViewState: initialViewState,
});

```

## Comment permissions

There might be situations where you want to disable the creation or deletion of individual comments based on some condition. To do this, you can define the [`isEditableComment`](https://www.nutrient.io/api/web/interfaces/Configuration.html#iseditablecomment) function as a configuration option when initializing the viewer. When the return value of the [`isEditableComment`](https://www.nutrient.io/api/web/interfaces/Configuration.html#iseditablecomment) method is `false` for a comment, the comment can no longer be deleted by the user. Similarly, [`isEditableComment`](https://www.nutrient.io/api/web/interfaces/Configuration.html#iseditablecomment) can be used to determine whether or not a user can reply to existing threads.

In the example below, all the comments that have a root annotation with an `id` other than the `rootAnnotationId` will be editable:

```js

NutrientViewer.load({
  //...
  isEditableComment: (comment) => {
    return comment.rootId!== rootAnnotationId;
  },
});

```

For every comment thread, [`isEditableComment`](https://www.nutrient.io/api/web/interfaces/Configuration.html#iseditablecomment) receives a temporary draft comment with `pageIndex=null`. The `rootId` of this draft comment points to the root annotation of the comment thread. If [`isEditableComment`](https://www.nutrient.io/api/web/interfaces/Configuration.html#iseditablecomment) returns `false`, the user won’t be able to add comments in that comment thread.

In the example below, a user can’t add a comment in any comment thread:

```js

NutrientViewer.load({
  //...
  isEditableComment: (comment) => {
    return comment.pageIndex!== null;
  },
});

```

To set the permissions after a viewer instance has been created, you can use [`instance.setIsEditableComment`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#setiseditablecomment):

```js

NutrientViewer.load(options).then((instance) => {
  instance.setIsEditableComment((comment) => {
    return comment.rootId!== rootAnnotationId;
  });
});

```

## Customizing a comment block

Customize the look of a comment block using CSS. Make sure you’re using the public class names starting with `PSPDFKit-`, as other class names might change in the future and break your application.

For example, if you want to customize the `border-radius` of avatars, you can do that by writing the following CSS:

```css.PSPDFKit-Comment-Avatar {
  border-radius: 6px;
}

```

You can also show avatars in comment blocks by setting a custom renderer:

```js

NutrientViewer.load({
    customRenderers: {
        CommentAvatar: (comment: Comment) => ({
            node: element,
            append: false, // This should always be `false` in this case.
        }),
    },
});

```

If you want to change the avatar after the viewer instance has been created, you can use [`setCustomRenderers`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#setcustomrenderers).

## Responsive UI

The comments UI adapts to the screen size of your browser. When there’s enough space in the viewport outside of the current page, comment threads are displayed in a sidebar alongside the page and are always visible. When the remaining viewport space is too small for floating comment threads to be visible, they’re concealed until the user clicks or taps a root annotation.

This default behavior can be customized by modifying the value of `ViewState#commentDisplay`, which accepts the following values:

- `NutrientViewer.CommentDisplay.FITTING` — The default value; shows comments in a floating sidebar by the page when there’s enough space, and in a popover otherwise, when the marker annotation is selected.

- `NutrientViewer.CommentDisplay.FLOATING` — Will always show comments in a floating sidebar by the page side, except when `ViewState#zoom` is set to `NutrientViewer.ZoomMode.FIT_TO_WIDTH`, in which case, they’re displayed in a popover dialog instead.

- `NutrientViewer.CommentDisplay.POPOVER` — Will always show comments in a popover when the marker annotation is selected.

These values only affect desktop and tablet screens; on mobile devices, comments are always displayed in a drawer at the bottom of the viewport.

Here’s an example of how to change it:

```js

NutrientViewer.load({...config,
  initialViewState: new NutrientViewer.ViewState({
    commentDisplay: NutrientViewer.CommentDisplay.FLOATING,
  }),
});

```

## Rich text comments

Rich text editing is supported in comments. Using the UI, you can select parts of a comment and do the following:

- Make parts of the comment bold, italic, or underlined.

- Change the color and the background color of the parts of the comment.

- Add hyperlinks to the comment.
---

## Related pages

- [Reply to annotations in our JavaScript PDF viewer](/guides/web/annotations/comments-and-replies/replies.md)

