Excalidraw

PreviousNext

Void Excalidraw drawing blocks stored inside Plate values.

Excalidraw adds a void excalidraw element that embeds the Excalidraw canvas in the editor. The node stores Excalidraw elements and state under data. This page covers kit setup, insertion, persistence shape, and the client-only registry UI.

Loading…

Features

  • Void excalidraw block element.
  • Direct insertExcalidraw(editor, props, options) helper.
  • Excalidraw elements and app state stored on the node.
  • Dynamic Excalidraw component loading in the React hook.
  • Change deduplication before writing canvas data back to Slate.
  • Read-only mode through Excalidraw viewModeEnabled.

Fast Path

Add The Kit

ExcalidrawKit installs ExcalidrawPlugin with the registry ExcalidrawElement.

'use client';
 
import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
 
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
 
export const ExcalidrawKit = [
  ExcalidrawPlugin.withComponent(ExcalidrawElement),
];
'use client';
 
import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
 
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
 
export const ExcalidrawKit = [
  ExcalidrawPlugin.withComponent(ExcalidrawElement),
];
import { createPlateEditor } from 'platejs/react';
 
import { ExcalidrawKit } from '@/components/editor/plugins/excalidraw-kit';
 
export const editor = createPlateEditor({
  plugins: ExcalidrawKit,
});
import { createPlateEditor } from 'platejs/react';
 
import { ExcalidrawKit } from '@/components/editor/plugins/excalidraw-kit';
 
export const editor = createPlateEditor({
  plugins: ExcalidrawKit,
});

Render The Element

excalidraw-node owns the client component, Excalidraw CSS import, fixed canvas frame, and read-only view mode.

'use client';
 
import * as React from 'react';
 
import type { TExcalidrawElement } from '@platejs/excalidraw';
import type { PlateElementProps } from 'platejs/react';
 
import { useExcalidrawElement } from '@platejs/excalidraw/react';
import { PlateElement, useReadOnly } from 'platejs/react';
 
import { cn } from '@/lib/utils';
 
import '@excalidraw/excalidraw/index.css';
 
export function ExcalidrawElement(
  props: PlateElementProps<TExcalidrawElement>
) {
  const { children, element } = props;
  const readOnly = useReadOnly();
 
  const { Excalidraw, excalidrawProps } = useExcalidrawElement({
    element,
  });
 
  return (
    <PlateElement {...props}>
      <div contentEditable={false}>
        <div
          className={cn(
            'mx-auto aspect-video h-[600px] w-[min(100%,600px)] overflow-hidden rounded-sm border'
          )}
        >
          {Excalidraw && (
            <Excalidraw
              {...(excalidrawProps as any)}
              viewModeEnabled={readOnly}
            />
          )}
        </div>
      </div>
      {children}
    </PlateElement>
  );
}
'use client';
 
import * as React from 'react';
 
import type { TExcalidrawElement } from '@platejs/excalidraw';
import type { PlateElementProps } from 'platejs/react';
 
import { useExcalidrawElement } from '@platejs/excalidraw/react';
import { PlateElement, useReadOnly } from 'platejs/react';
 
import { cn } from '@/lib/utils';
 
import '@excalidraw/excalidraw/index.css';
 
export function ExcalidrawElement(
  props: PlateElementProps<TExcalidrawElement>
) {
  const { children, element } = props;
  const readOnly = useReadOnly();
 
  const { Excalidraw, excalidrawProps } = useExcalidrawElement({
    element,
  });
 
  return (
    <PlateElement {...props}>
      <div contentEditable={false}>
        <div
          className={cn(
            'mx-auto aspect-video h-[600px] w-[min(100%,600px)] overflow-hidden rounded-sm border'
          )}
        >
          {Excalidraw && (
            <Excalidraw
              {...(excalidrawProps as any)}
              viewModeEnabled={readOnly}
            />
          )}
        </div>
      </div>
      {children}
    </PlateElement>
  );
}

Add An Insert Action

The registry insert toolbar maps KEYS.excalidraw to insertExcalidraw(editor, {}, { select: true }).

components/editor/transforms.ts
import { insertExcalidraw } from '@platejs/excalidraw';
import { KEYS } from 'platejs';
 
export const insertBlockMap = {
  [KEYS.excalidraw]: (editor) =>
    insertExcalidraw(editor, {}, { select: true }),
};
components/editor/transforms.ts
import { insertExcalidraw } from '@platejs/excalidraw';
import { KEYS } from 'platejs';
 
export const insertBlockMap = {
  [KEYS.excalidraw]: (editor) =>
    insertExcalidraw(editor, {}, { select: true }),
};

Ownership

LayerOwnerWhat It Does
@platejs/excalidrawPackageExports BaseExcalidrawPlugin, TExcalidrawElement, ExcalidrawDataState, and insertExcalidraw.
@platejs/excalidraw/reactPackageExports ExcalidrawPlugin, useExcalidrawElement, and React Excalidraw prop types.
excalidraw-kitRegistryAdds ExcalidrawPlugin.withComponent(ExcalidrawElement).
excalidraw-nodeRegistry UIDynamically renders @excalidraw/excalidraw inside a Plate element.
App persistenceApp codeStores the Plate value that contains Excalidraw element data.

ExcalidrawPlugin does not bind an editor.tf.insert.excalidraw transform. Use insertExcalidraw directly.

Manual Setup

Install Package

pnpm add @platejs/excalidraw
pnpm add @platejs/excalidraw

Add The Plugin

Use the React plugin when the editor renders the Excalidraw canvas.

import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
import { createPlateEditor } from 'platejs/react';
 
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
 
export const editor = createPlateEditor({
  plugins: [ExcalidrawPlugin.withComponent(ExcalidrawElement)],
});
import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
import { createPlateEditor } from 'platejs/react';
 
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
 
export const editor = createPlateEditor({
  plugins: [ExcalidrawPlugin.withComponent(ExcalidrawElement)],
});

Insert A Drawing

insertExcalidraw inserts after the current selection parent with nextBlock: true. If the editor has no selection or no selection parent, it returns without inserting.

import { insertExcalidraw } from '@platejs/excalidraw';
 
insertExcalidraw(
  editor,
  {
    data: {
      elements: [],
      state: {
        viewBackgroundColor: '#ffffff',
      },
    },
  },
  { select: true }
);
import { insertExcalidraw } from '@platejs/excalidraw';
 
insertExcalidraw(
  editor,
  {
    data: {
      elements: [],
      state: {
        viewBackgroundColor: '#ffffff',
      },
    },
  },
  { select: true }
);

Value Shape

TExcalidrawElement is a void element. The drawing payload lives in data, not in text children.

const value = [
  {
    children: [{ text: '' }],
    data: {
      elements: [
        {
          id: 'shape-1',
          type: 'rectangle',
          x: 100,
          y: 100,
        },
      ],
      state: {
        viewBackgroundColor: '#ffffff',
      },
    },
    type: 'excalidraw',
  },
];
const value = [
  {
    children: [{ text: '' }],
    data: {
      elements: [
        {
          id: 'shape-1',
          type: 'rectangle',
          x: 100,
          y: 100,
        },
      ],
      state: {
        viewBackgroundColor: '#ffffff',
      },
    },
    type: 'excalidraw',
  },
];
FieldTypeNotes
type'excalidraw'Plugin key and node type from KEYS.excalidraw.
children[{ text: '' }]Required Slate child for the void element.
data.elementsExcalidraw elementsStored as partial Excalidraw elements.
data.stateExcalidraw app stateStored as imported Excalidraw app state.

Markdown serialization is not owned by @platejs/excalidraw. Persist the Plate value when you need to keep drawings.

UI Behavior

useExcalidrawElement bridges the Plate node and the Excalidraw React component.

SurfaceBehavior
Component loadingDynamically imports @excalidraw/excalidraw and returns the loaded component.
Initial dataDeep-clones element.data.state, element.data.elements, libraryItems, and scrollToContent.
EditingonChange writes { elements, state } back to the node.
DeduplicationUses deep equality to skip writes when canvas data did not change.
Read-only modeRemoves the write handler and enables Excalidraw viewModeEnabled.
Canvas frameRegistry UI renders a bordered aspect-video frame capped at 600px.

The registry element imports @excalidraw/excalidraw/index.css, so custom copies need the same stylesheet.

API Reference

APIPackageUse
BaseExcalidrawPlugin@platejs/excalidrawHeadless void element plugin.
ExcalidrawPlugin@platejs/excalidraw/reactReact Excalidraw plugin.
insertExcalidraw(editor, props?, options?)@platejs/excalidrawInserts a void Excalidraw node after the current selection parent.
useExcalidrawElement(options)@platejs/excalidraw/reactReturns the dynamically loaded Excalidraw component and props for the registry element.
TExcalidrawElement@platejs/excalidrawElement shape with optional data.
ExcalidrawDataState@platejs/excalidrawData shape for stored Excalidraw elements and app state.