Loading...
Files
components/demo.tsx
'use client';

import * as React from 'react';

import { Plate, usePlateEditor } from 'platejs/react';

import { EditorKit } from '@/components/editor/editor-kit';
import { Editor, EditorContainer } from '@/components/ui/editor';

import { DEMO_VALUES } from './values/demo-values';

export default function Demo({ id }: { id: string }) {
  const editor = usePlateEditor({
    plugins: EditorKit,
    value: DEMO_VALUES[id],
  });

  return (
    <Plate editor={editor}>
      <EditorContainer variant="demo">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Features

  • Text Comments: Add comments as text marks with inline annotations
  • Overlapping Comments: Support multiple comments on the same text
  • Draft Comments: Create draft comments before finalizing
  • State Tracking: Track comment state and user interactions
  • Discussion Integration: Works with discussion plugin for complete collaboration

Kit Usage

Installation

The fastest way to add comment functionality is with the CommentKit, which includes pre-configured commentPlugin and related components along with their Plate UI components.

'use client';
 
import type { ExtendConfig, Path } from 'platejs';
 
import {
  type BaseCommentConfig,
  BaseCommentPlugin,
  getDraftCommentKey,
} from '@platejs/comment';
import { isSlateString } from 'platejs';
import { toTPlatePlugin } from 'platejs/react';
 
import { CommentLeaf } from '@/components/ui/comment-node';
 
type CommentConfig = ExtendConfig<
  BaseCommentConfig,
  {
    activeId: string | null;
    commentingBlock: Path | null;
    hotkey: string[];
    hoverId: string | null;
    uniquePathMap: Map<string, Path>;
  }
>;
 
export const commentPlugin = toTPlatePlugin<CommentConfig>(BaseCommentPlugin, {
  handlers: {
    onClick: ({ api, event, setOption, type }) => {
      let leaf = event.target as HTMLElement;
      let isSet = false;
 
      const unsetActiveSuggestion = () => {
        setOption('activeId', null);
        isSet = true;
      };
 
      if (!isSlateString(leaf)) unsetActiveSuggestion();
 
      while (leaf.parentElement) {
        if (leaf.classList.contains(`slate-${type}`)) {
          const commentsEntry = api.comment!.node();
 
          if (!commentsEntry) {
            unsetActiveSuggestion();
 
            break;
          }
 
          const id = api.comment!.nodeId(commentsEntry[0]);
 
          setOption('activeId', id ?? null);
          isSet = true;
 
          break;
        }
 
        leaf = leaf.parentElement;
      }
 
      if (!isSet) unsetActiveSuggestion();
    },
  },
  options: {
    activeId: null,
    commentingBlock: null,
    hoverId: null,
    uniquePathMap: new Map(),
  },
})
  .extendTransforms(
    ({
      editor,
      setOption,
      tf: {
        comment: { setDraft },
      },
    }) => ({
      setDraft: () => {
        if (editor.api.isCollapsed()) {
          editor.tf.select(editor.api.block()![1]);
        }
 
        setDraft();
 
        editor.tf.collapse();
        setOption('activeId', getDraftCommentKey());
        setOption('commentingBlock', editor.selection!.focus.path.slice(0, 1));
      },
    })
  )
  .configure({
    node: { component: CommentLeaf },
    shortcuts: {
      setDraft: { keys: 'mod+shift+m' },
    },
  });
 
export const CommentKit = [commentPlugin.withComponent(CommentLeaf)];

Add Kit

import { createPlateEditor } from 'platejs/react';
import { CommentKit } from '@/components/editor/plugins/comment-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...CommentKit,
  ],
});

Manual Usage

Installation

pnpm add @platejs/comment

Extend Comment Plugin

Create the comment plugin with extended configuration for state management:

import {
  type ExtendConfig,
  type Path,
  isSlateString,
} from 'platejs';
import {
  type BaseCommentConfig,
  BaseCommentPlugin,
  getDraftCommentKey,
} from '@platejs/comment';
import { toTPlatePlugin } from 'platejs/react';
import { CommentLeaf } from '@/components/ui/comment-node';
 
type CommentConfig = ExtendConfig<
  BaseCommentConfig,
  {
    activeId: string | null;
    commentingBlock: Path | null;
    hoverId: string | null;
    uniquePathMap: Map<string, Path>;
  }
>;
 
export const commentPlugin = toTPlatePlugin<CommentConfig>(
  BaseCommentPlugin,
  ({ editor }) => ({
    options: {
      activeId: null,
      commentingBlock: null,
      hoverId: null,
      uniquePathMap: new Map(),
    },
    render: {
      node: CommentLeaf,
    },
  })
);
  • options.activeId: Currently active comment ID for visual highlighting
  • options.commentingBlock: Path of the block currently being commented
  • options.hoverId: Currently hovered comment ID for hover effects
  • options.uniquePathMap: Map tracking unique paths for comment resolution
  • render.node: Assigns CommentLeaf to render comment text marks

Add Click Handler

Add click handling to manage active comment state:

export const commentPlugin = toTPlatePlugin<CommentConfig>(
  BaseCommentPlugin,
  ({ editor }) => ({
    handlers: {
      // Set active comment when clicking on comment marks
      onClick: ({ api, event, setOption, type }) => {
        let leaf = event.target as HTMLElement;
        let isSet = false;
 
        const unsetActiveComment = () => {
          setOption('activeId', null);
          isSet = true;
        };
 
        if (!isSlateString(leaf)) unsetActiveComment();
 
        while (leaf.parentElement) {
          if (leaf.classList.contains(`slate-${type}`)) {
            const commentsEntry = api.comment.node();
 
            if (!commentsEntry) {
              unsetActiveComment();
              break;
            }
 
            const id = api.comment.nodeId(commentsEntry[0]);
            setOption('activeId', id ?? null);
            isSet = true;
            break;
          }
 
          leaf = leaf.parentElement;
        }
 
        if (!isSet) unsetActiveComment();
      },
    },
    // ... previous options and render
  })
);

The click handler tracks which comment is currently active:

  • Detects comment clicks: Traverses DOM to find comment elements
  • Sets active state: Updates activeId when clicking on comments
  • Clears state: Unsets activeId when clicking outside comments
  • Visual feedback: Enables hover/active styling in comment components

Extend Transforms

Extend the setDraft transform for enhanced functionality:

export const commentPlugin = toTPlatePlugin<CommentConfig>(
  BaseCommentPlugin,
  ({ editor }) => ({
    // ... previous configuration
  })
)
  .extendTransforms(
    ({
      editor,
      setOption,
      tf: {
        comment: { setDraft },
      },
    }) => ({
      setDraft: () => {
        if (editor.api.isCollapsed()) {
          editor.tf.select(editor.api.block()![1]);
        }
 
        setDraft();
 
        editor.tf.collapse();
        setOption('activeId', getDraftCommentKey());
        setOption('commentingBlock', editor.selection!.focus.path.slice(0, 1));
      },
    })
  )
  .configure({
    node: { component: CommentLeaf },
    shortcuts: {
      setDraft: { keys: 'mod+shift+m' },
    },
  });

Add Toolbar Button

You can add CommentToolbarButton to your Toolbar to add comments on selected text.

Add Plugins

import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    commentPlugin,
  ],
});

Discussion Integration

The comment plugin works with the discussion plugin for complete collaboration:

import { discussionPlugin } from '@/components/editor/plugins/discussion-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    discussionPlugin,
    commentPlugin,
  ],
});

Keyboard Shortcuts

KeyDescription
Cmd + Shift + M

Add a comment on the selected text.

Plate Plus

  • Full stack example for Suggestion and Comment
  • Floating comments & suggestions UI with better user experience
  • Comment rendered with Plate editor
  • Discussion list in the sidebar

Plugins

CommentPlugin

Plugin for creating and managing text comments with state tracking and discussion integration.

Options

Collapse all

    Currently active comment ID for visual highlighting. Used internally to track state.

    Path of the block currently being commented on.

    Currently hovered comment ID for hover effects.

    Map tracking unique paths for comment resolution.

API

api.comment.has

Checks if a comment with the given ID exists in the editor.

Parameters

Collapse all

    Options containing the comment ID to check.

Returnsboolean

    Whether the comment exists.

api.comment.node

Gets a comment node entry.

OptionsEditorNodesOptions & { id?: string; isDraft?: boolean }

    Options for finding the node.

ReturnsNodeEntry<TCommentText> | undefined

    The comment node entry if found.

api.comment.nodeId

Gets the ID of a comment from a leaf node.

Parameters

Collapse all

    The comment leaf node.

Returnsstring | undefined

    The comment ID if found.

api.comment.nodes

Gets all comment node entries matching the options.

OptionsEditorNodesOptions & { id?: string; isDraft?: boolean }

    Options for finding the nodes.

ReturnsNodeEntry<TCommentText>[]

    Array of comment node entries.

Transforms

tf.comment.removeMark

Removes the comment mark from the current selection or a specified location.

tf.comment.setDraft

Sets a draft comment mark at the current selection.

OptionsSetNodesOptions

    Options for setting the draft comment.

tf.comment.unsetMark

Unsets comment nodes with the specified ID from the editor.

Parameters

Collapse all

    Options containing the comment ID to unset.

Utilities

getCommentCount

Gets the count of non-draft comments in a comment node.

Parameters

Collapse all

    The comment node.

Returnsnumber

    The count of comments.

getCommentKey

Generates a comment key based on the provided ID.

Parameters

Collapse all

    The ID of the comment.

Returnsstring

    The generated comment key.

getCommentKeyId

Extracts the comment ID from a comment key.

Parameters

Collapse all

    The comment key.

Returnsstring

    The extracted comment ID.

getCommentKeys

Returns an array of comment keys present in the given node.

Parameters

Collapse all

    The node to check for comment keys.

Returnsstring[]

    Array of comment keys.

getDraftCommentKey

Gets the key used for draft comments.

Returnsstring

    The draft comment key.

isCommentKey

Checks if a given key is a comment key.

Parameters

Collapse all

    The key to check.

Returnsboolean

    Whether the key is a comment key.

isCommentNodeById

Checks if a given node is a comment with the specified ID.

Parameters

Collapse all

    The node to check.

    The ID of the comment.

Returnsboolean

    Whether the node is a comment with the specified ID.

Types

TCommentText

Text nodes that can contain comments.

Attributes

Collapse all

    Whether this text node contains comments.

    Comment data keyed by comment ID. Multiple comments can exist in one text node.