'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 Suggestions: Add suggestions as text marks with inline annotations
- Block Suggestions: Create suggestions for entire blocks of content
- State Tracking: Track suggestion state and user interactions
- Undo/Redo Support: Full undo/redo support for suggestion changes
- Discussion Integration: Works with discussion plugin for complete collaboration
Kit Usage
Installation
The fastest way to add suggestion functionality is with the SuggestionKit
, which includes pre-configured SuggestionPlugin
and related components along with their Plate UI components.
'use client';
import {
type BaseSuggestionConfig,
BaseSuggestionPlugin,
} from '@platejs/suggestion';
import {
type ExtendConfig,
type Path,
isSlateEditor,
isSlateElement,
isSlateString,
} from 'platejs';
import { toTPlatePlugin } from 'platejs/react';
import { BlockSuggestion } from '@/components/ui/block-suggestion';
import {
SuggestionLeaf,
SuggestionLineBreak,
} from '@/components/ui/suggestion-node';
import { discussionPlugin } from './discussion-kit';
export type SuggestionConfig = ExtendConfig<
BaseSuggestionConfig,
{
activeId: string | null;
hoverId: string | null;
uniquePathMap: Map<string, Path>;
}
>;
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: editor.getOption(discussionPlugin, 'currentUserId'),
hoverId: null,
uniquePathMap: new Map(),
},
})
).configure({
handlers: {
// unset active suggestion when clicking outside of suggestion
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 &&
!isSlateElement(leaf.parentElement) &&
!isSlateEditor(leaf.parentElement)
) {
if (leaf.classList.contains(`slate-${type}`)) {
const suggestionEntry = api.suggestion!.node({ isText: true });
if (!suggestionEntry) {
unsetActiveSuggestion();
break;
}
const id = api.suggestion!.nodeId(suggestionEntry[0]);
setOption('activeId', id ?? null);
isSet = true;
break;
}
leaf = leaf.parentElement;
}
if (!isSet) unsetActiveSuggestion();
},
},
render: {
belowNodes: SuggestionLineBreak as any,
node: SuggestionLeaf,
belowRootNodes: ({ api, element }) => {
if (!api.suggestion!.isBlockSuggestion(element)) {
return null;
}
return <BlockSuggestion element={element} />;
},
},
});
export const SuggestionKit = [suggestionPlugin];
SuggestionLeaf
: Renders suggestion text marksBlockSuggestion
: Renders block-level suggestionsSuggestionLineBreak
: Handles line breaks in suggestions
Add Kit
import { createPlateEditor } from 'platejs/react';
import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
...SuggestionKit,
],
});
Manual Usage
Installation
pnpm add @platejs/suggestion
Extend Suggestion Plugin
Create the suggestion plugin with extended configuration for state management:
import {
type ExtendConfig,
type Path,
isSlateEditor,
isSlateElement,
isSlateString,
} from 'platejs';
import {
type BaseSuggestionConfig,
BaseSuggestionPlugin,
} from '@platejs/suggestion';
import { createPlatePlugin, toTPlatePlugin } from 'platejs/react';
import { BlockSuggestion } from '@/components/ui/block-suggestion';
import { SuggestionLeaf } from '@/components/ui/suggestion-node';
export type SuggestionConfig = ExtendConfig<
BaseSuggestionConfig,
{
activeId: string | null;
hoverId: string | null;
uniquePathMap: Map<string, Path>;
}
>;
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: 'alice', // Set your current user ID
hoverId: null,
uniquePathMap: new Map(),
},
render: {
node: SuggestionLeaf,
belowRootNodes: ({ api, element }) => {
if (!api.suggestion!.isBlockSuggestion(element)) {
return null;
}
return <BlockSuggestion element={element} />;
},
},
})
);
options.activeId
: Currently active suggestion ID for visual highlightingoptions.currentUserId
: ID of the current user creating suggestionsoptions.hoverId
: Currently hovered suggestion ID for hover effectsoptions.uniquePathMap
: Map tracking unique paths for suggestion resolutionrender.node
: AssignsSuggestionLeaf
to render suggestion text marksrender.belowRootNodes
: RendersBlockSuggestion
for block-level suggestions
Add Click Handler
Add click handling to manage active suggestion state:
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
handlers: {
// Unset active suggestion when clicking outside of suggestion
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 &&
!isSlateElement(leaf.parentElement) &&
!isSlateEditor(leaf.parentElement)
) {
if (leaf.classList.contains(`slate-${type}`)) {
const suggestionEntry = api.suggestion!.node({ isText: true });
if (!suggestionEntry) {
unsetActiveSuggestion();
break;
}
const id = api.suggestion!.nodeId(suggestionEntry[0]);
setOption('activeId', id ?? null);
isSet = true;
break;
}
leaf = leaf.parentElement;
}
if (!isSet) unsetActiveSuggestion();
},
},
// ... previous options and render
})
);
The click handler tracks which suggestion is currently active:
- Detects suggestion clicks: Traverses DOM to find suggestion elements
- Sets active state: Updates
activeId
when clicking on suggestions - Clears state: Unsets
activeId
when clicking outside suggestions - Visual feedback: Enables hover/active styling in suggestion components
Add Plugins
import { createPlateEditor, createPlatePlugin } from 'platejs/react';
import { SuggestionLineBreak } from '@/components/ui/suggestion-node';
const suggestionLineBreakPlugin = createPlatePlugin({
key: 'suggestionLineBreak',
render: { belowNodes: SuggestionLineBreak as any },
});
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
suggestionPlugin,
suggestionLineBreakPlugin,
],
});
render.belowNodes
: RendersSuggestionLineBreak
below nodes to handle line break suggestions
Enable Suggestion Mode
Use the plugin's API to control suggestion mode:
import { useEditorRef, usePluginOption } from 'platejs/react';
function SuggestionToolbar() {
const editor = useEditorRef();
const isSuggesting = usePluginOption(suggestionPlugin, 'isSuggesting');
const toggleSuggesting = () => {
editor.setOption(suggestionPlugin, 'isSuggesting', !isSuggesting);
};
return (
<button onClick={toggleSuggesting}>
{isSuggesting ? 'Stop Suggesting' : 'Start Suggesting'}
</button>
);
}
Add Toolbar Button
You can add SuggestionToolbarButton
to your Toolbar to toggle suggestion mode in the editor.
Discussion Integration
The suggestion plugin works with the discussion plugin for complete collaboration:
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
discussionPlugin,
suggestionPlugin.configure({
options: {
currentUserId: 'alice',
},
}),
suggestionLineBreakPlugin,
],
});
Keyboard Shortcuts
Key | Description |
---|---|
Cmd + Shift + S | Add a suggestion 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
SuggestionPlugin
Plugin for creating and managing text and block suggestions with state tracking and discussion integration.
API
api.suggestion.dataList
Gets suggestion data from a text node.
api.suggestion.isBlockSuggestion
Checks if a node is a block suggestion element.
api.suggestion.node
Gets a suggestion node entry.
api.suggestion.nodeId
Gets the ID of a suggestion from a node.
api.suggestion.nodes
Gets all suggestion node entries matching the options.
api.suggestion.suggestionData
Gets suggestion data from a node.
api.suggestion.withoutSuggestions
Temporarily disables suggestions while executing a function.
Types
TSuggestionText
Text nodes that can contain suggestions.
TSuggestionElement
Block elements that contain suggestion metadata.
TInlineSuggestionData
Data structure for inline text suggestions.
TSuggestionData
Data structure for block-level suggestions.
On This Page
FeaturesKit UsageInstallationAdd KitManual UsageInstallationExtend Suggestion PluginAdd Click HandlerAdd PluginsEnable Suggestion ModeAdd Toolbar ButtonDiscussion IntegrationKeyboard ShortcutsPlate PlusPluginsSuggestionPluginAPIapi.suggestion.dataListapi.suggestion.isBlockSuggestionapi.suggestion.nodeapi.suggestion.nodeIdapi.suggestion.nodesapi.suggestion.suggestionDataapi.suggestion.withoutSuggestionsTypesTSuggestionTextTSuggestionElementTInlineSuggestionDataTSuggestionData