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)];
CommentLeaf
: Renders comment text marksBlockDiscussion
: Renders discussion UI with comments integration
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 highlightingoptions.commentingBlock
: Path of the block currently being commentedoptions.hoverId
: Currently hovered comment ID for hover effectsoptions.uniquePathMap
: Map tracking unique paths for comment resolutionrender.node
: AssignsCommentLeaf
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
Key | Description |
---|---|
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.
API
api.comment.has
Checks if a comment with the given ID exists in the editor.
api.comment.node
Gets a comment node entry.
api.comment.nodeId
Gets the ID of a comment from a leaf node.
api.comment.nodes
Gets all comment node entries matching the options.
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.
tf.comment.unsetMark
Unsets comment nodes with the specified ID from the editor.
Utilities
getCommentCount
Gets the count of non-draft comments in a comment node.
getCommentKey
Generates a comment key based on the provided ID.
getCommentKeyId
Extracts the comment ID from a comment key.
getCommentKeys
Returns an array of comment keys present in the given node.
getDraftCommentKey
Gets the key used for draft comments.
isCommentKey
Checks if a given key is a comment key.
isCommentNodeById
Checks if a given node is a comment with the specified ID.
Types
TCommentText
Text nodes that can contain comments.
On This Page
FeaturesKit UsageInstallationAdd KitManual UsageInstallationExtend Comment PluginAdd Click HandlerExtend TransformsAdd Toolbar ButtonAdd PluginsDiscussion IntegrationKeyboard ShortcutsPlate PlusPluginsCommentPluginAPIapi.comment.hasapi.comment.nodeapi.comment.nodeIdapi.comment.nodesTransformstf.comment.removeMarktf.comment.setDrafttf.comment.unsetMarkUtilitiesgetCommentCountgetCommentKeygetCommentKeyIdgetCommentKeysgetDraftCommentKeyisCommentKeyisCommentNodeByIdTypesTCommentText