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 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];

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 highlighting
  • options.currentUserId: ID of the current user creating suggestions
  • options.hoverId: Currently hovered suggestion ID for hover effects
  • options.uniquePathMap: Map tracking unique paths for suggestion resolution
  • render.node: Assigns SuggestionLeaf to render suggestion text marks
  • render.belowRootNodes: Renders BlockSuggestion 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,
  ],
});

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

KeyDescription
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.

Options

Collapse all

    ID of the current user creating suggestions. Required for proper suggestion attribution.

    Whether the editor is currently in suggestion mode. Used internally to track state.

API

api.suggestion.dataList

Gets suggestion data from a text node.

Parameters

Collapse all

    The suggestion text node.

ReturnsTInlineSuggestionData[]

    Array of suggestion data.

api.suggestion.isBlockSuggestion

Checks if a node is a block suggestion element.

Parameters

Collapse all

    The node to check.

Returnsnode is TSuggestionElement

    Whether the node is a block suggestion.

api.suggestion.node

Gets a suggestion node entry.

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

    Options for finding the node.

ReturnsNodeEntry<TSuggestionElement | TSuggestionText> | undefined

    The suggestion node entry if found.

api.suggestion.nodeId

Gets the ID of a suggestion from a node.

Parameters

Collapse all

    The node to get ID from.

Returnsstring | undefined

    The suggestion ID if found.

api.suggestion.nodes

Gets all suggestion node entries matching the options.

OptionsEditorNodesOptions

    Options for finding the nodes.

ReturnsNodeEntry<TElement | TSuggestionText>[]

    Array of suggestion node entries.

api.suggestion.suggestionData

Gets suggestion data from a node.

Parameters

Collapse all

    The node to get suggestion data from.

ReturnsTInlineSuggestionData | TSuggestionElement['suggestion'] | undefined

    The suggestion data if found.

api.suggestion.withoutSuggestions

Temporarily disables suggestions while executing a function.

Parameters

Collapse all

    The function to execute.

Types

TSuggestionText

Text nodes that can contain suggestions.

Attributes

Collapse all

    Whether this is a suggestion.

    Suggestion data. Multiple suggestions can exist in one text node.

TSuggestionElement

Block elements that contain suggestion metadata.

Attributes

Collapse all

    Block-level suggestion data including type, user, and timing information.

TInlineSuggestionData

Data structure for inline text suggestions.

Attributes

Collapse all

    Unique identifier for the suggestion.

    ID of the user who created the suggestion.

    Timestamp when the suggestion was created.

    Type of suggestion operation.

    For update suggestions, the new mark properties being suggested.

    For update suggestions, the previous mark properties.

TSuggestionData

Data structure for block-level suggestions.

Attributes

Collapse all

    Unique identifier for the suggestion.

    ID of the user who created the suggestion.

    Timestamp when the suggestion was created.

    Type of block suggestion operation.

    Whether this suggestion represents a line break insertion.