Comments

Read, create, reply to, and resolve DOCX comment threads with the packaged review rail or useReview.

Comments attach a discussion thread to a text range. They share the review rail, the useReview() hook, and the card layout with tracked changes. Comments require reviewModule() from @docx-editor.dev/pro, which uses the EigenPal Pro License.

Add a comment

comment(text, author?) comments on the current selection. This example keeps the draft when the editor refuses the write:

import { useState } from 'react';
import { useReview } from '@docx-editor.dev/pro/react';

function CommentBox() {
  const { comment, selectionAnchorY } = useReview();
  const [text, setText] = useState('');

  // null when nothing is selected: there is nowhere to anchor a comment.
  if (selectionAnchorY === null) return null;

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        if (comment(text)) setText('');
      }}
    >
      <textarea value={text} onChange={(e) => setText(e.target.value)} />
      <button type="submit" disabled={!text.trim()}>
        Comment
      </button>
    </form>
  );
}

A refused write returns false and does not throw. selectionAnchorY is the selection's document-space Y coordinate.

The packaged DocxEditorReview.AddComment and DocxEditorReview.Draft parts provide this workflow. For placement in custom cards, see Custom cards in the review rail.

Read and update threads

useReview().items holds comments and revisions. This example lists comments with a reply button:

import { useReview } from '@docx-editor.dev/pro/react';

function Threads() {
  const { items, reply } = useReview();
  return (
    <ul>
      {items
        .filter((item) => item.kind === 'comment')
        .map((item) => (
          <li key={item.key}>
            {/* File-derived. Render as text, never as markup. */}
            {item.text}: {item.author}
            <button onClick={() => reply(item, 'Agreed.')}>Reply</button>
          </li>
        ))}
    </ul>
  );
}

Each action returns whether the editor applied it. commentResolutionDisabledReason explains why resolve and reopen are unavailable, such as in viewing mode:

ActionResultRefusal behavior
comment(text, author?)Adds a thread to the selectionReturns false; the caller keeps its draft
reply(item, text, author?)Adds a reply, or comments on a revision rangeReturns false
resolve(item)Marks an open thread completeRepeating it succeeds without a write. Returns false for a stale or non-comment item.
reopen(item)Reopens a complete threadRepeating it succeeds without a write. Returns false for a stale or non-comment item.
remove(item)Deletes a comment thread or rejects a tracked changeReturns false when refused; a refused tracked change sets lastRefusal.action to reject

Comment items include these fields:

FieldMeaning
key, idStable review and OOXML identifiers
author, initials, dateComment author metadata
textFile-derived comment text
replyIdsReply identifiers in the thread
resolvedWhether w15:commentsEx marks the thread complete
parentIdParent comment identifier; absent on a thread root
anchorY, pageIndexDocument placement
isActiveWhether the caret is in the item

editor.getComments() on the core editor returns an empty array. For comment records, use getReviewItems() with the review module.

Show refused actions

lastRefusal from useReview() records the latest refused review action. This example shows the cause under a custom card:

import {
  useReview,
  useReviewRefusalMessage,
  type ReviewItemView,
} from '@docx-editor.dev/pro/react';

function RefusalNotice({ item }: { item: ReviewItemView }) {
  const { lastRefusal } = useReview();
  const refusalMessage = useReviewRefusalMessage();
  if (lastRefusal?.key !== item.key) return null;
  return <p>{refusalMessage(lastRefusal)}</p>;
}

lastRefusal names the refused action, the card key (null for a new comment), and the diagnostic code. final is true when the collaboration session ended, so a retry cannot help. sequence rises with each refusal, including repeats.

  • Every useReview() over one editor shares lastRefusal. The packaged card shows it with DocxEditorReview.Refusal.
  • The next applied action, or a change to the document, mode, editability, collaboration status, or author filter, clears it. A caret move does not.
  • A mounted DocxEditorReview announces every refusal in one polite live region. Do not add a second one.
  • Without DocxEditorReview, announce lastRefusal yourself, keyed on lastRefusal.sequence.

For every code and its remedy, see Diagnostics.

Save comment changes

Each comment or reply write emits the editor's change event. Use it for autosave and dirty tracking:

useEditorEvent('change', (change) => void autosave(change.revision));

The editor saves author, initials, date, reply threading, and resolved state in the comments part. Other DOCX applications can continue these threads.

Limits

  • Author: comment and reply require a non-empty author, because OOXML requires w:author.
  • Browser writes: All writes need reviewModule(), an attached editor, and an editable mode.
  • Protection: Comments-only protection permits comment actions. Read-only protection and mode="view" refuse writes. See Document protection.
  • Ranges: Empty comment text and cross-cell anchors are refused, never approximated.
  • Revisions: A reply to a revision becomes a comment over the revision range, because w:ins and w:del have no body.
  • Undo: Resolve and reopen write one transaction and one Undo entry. A repeated call creates none.
  • Suggesting mode: Resolve and reopen work in suggesting mode and create no tracked change.
  • Security: Render file-derived comment text as text, never as HTML.

See also