Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Plate
  • Editor API
  • Editor Transforms
  • Node
  • Element
  • Text
  • Path
  • Point
  • Range
  • Location
  • Anchor
  • Selection
  • Document Change
  • DOM API
  • React Hooks
  • Plate Core
    • Plate Components
    • Plate Editor
    • Plate Plugin
    • Editor Context
    • Plate Controller
  • Plate Utils
  • Resizable

Plate Components

PreviousNext

API reference for Plate React components.

Plate components connect an Editor to React rendering. Use EditorRoot and EditorContent for editable editors, EditorPreview for read-only static views, and the node primitives when writing custom plugin components.

Editable editor

EditorRoot owns one mounted editor view and provides its context. EditorContent renders the editable surface under that view.

components/editor.tsx
import { EditorRoot, EditorContent, useCreateEditor } from "platejs/react";
 
export function














Plate CorePlate Editor

On This Page

Editable editorAuthored viewRead-only viewEditor layoutMinimal editorScrollable editorFixed toolbar and scrolling contentComponent mapRender pipelineNode primitivesNode selectionAPI ReferenceEditorRootEditorContentEditorPreviewEditorContainerRender primitives
Build your editor
Production-ready AI template and reusable components.
Get all-access
Editor
() {
const editor = useCreateEditor({
initialValue: [
{
children: [{ text: "Start writing." }],
type: "paragraph",
},
],
});
return (
<EditorRoot editor={editor}>
<EditorContent placeholder="Write..." />
</EditorRoot>
);
}
components/editor.tsx
import { EditorRoot, EditorContent, useCreateEditor } from "platejs/react";
 
export function Editor() {
  const editor = useCreateEditor({
    initialValue: [
      {
        children: [{ text: "Start writing." }],
        type: "paragraph",
      },
    ],
  });
 
  return (
    <EditorRoot editor={editor}>
      <EditorContent placeholder="Write..." />
    </EditorRoot>
  );
}
Provider required

EditorContent must render below EditorRoot. useEditor() requires an active editor and throws otherwise. Use useOptionalEditor() only in controller UI that intentionally handles null while no editor is active.

Authored view

For an editor with authored changes, pass authored to choose its starting input intent and visible projection:

<EditorRoot
  editor={editor}
  authored={{ intent: 'edit', projection: 'markup' }}
>
  <EditorContent />
</EditorRoot>
<EditorRoot
  editor={editor}
  authored={{ intent: 'edit', projection: 'markup' }}
>
  <EditorContent />
</EditorRoot>
authored valueBehavior
{ intent: 'edit', projection: 'accepted' }Edit accepted content.
{ intent: 'edit', projection: 'markup' }Display review markup while publishing independent accepted-content edits directly.
{ intent: 'edit', projection: 'proposed' }Display the proposed result while publishing independent accepted-content edits directly.
{ intent: 'propose', projection: 'markup' }Propose edits and display retained review content.
{ intent: 'propose', projection: 'proposed' }Propose edits and display the proposed result.

The prop requires an installed authored capability. Each EditorRoot configures its own view, so two views of one document can use different values. Changing the intent or projection updates that view without remounting it. Rerendering with equivalent values preserves mode changes made through its mounted plugin portal. In an editing markup or proposed view, an edit that depends on pending content remains reviewable with the current author. The prop does not change the saved document or accept or reject suggestions.

Read-only view

Use EditorPreview with a static editor when you need rendered content and Plate copy behavior without an editable surface.

components/read-only-editor.tsx
import { EditorPreview, useStaticEditor } from "platejs/react";
 
const value = [
  {
    children: [{ text: "Published content." }],
    type: "paragraph",
  },
];
 
export function ReadOnlyEditor() {
  const editor = useStaticEditor({ initialValue: value });
 
  if (!editor) return null;
 
  return <EditorPreview editor={editor} />;
}
components/read-only-editor.tsx
import { EditorPreview, useStaticEditor } from "platejs/react";
 
const value = [
  {
    children: [{ text: "Published content." }],
    type: "paragraph",
  },
];
 
export function ReadOnlyEditor() {
  const editor = useStaticEditor({ initialValue: value });
 
  if (!editor) return null;
 
  return <EditorPreview editor={editor} />;
}

The default onCopy handler on EditorPreview writes Plate fragment data to the clipboard. Pass onCopy to supply your own handler.

Editor layout

EditorFrame and EditorContainer are optional. A minimal editor needs only EditorRoot and an editable surface: EditorContent from platejs/react, or the styled Editor from the Editor registry item.

LayoutCompositionWhere to set the height
Auto-growing, textarea-like editorEditorSet a minimum height on Editor.
Editor with its own scrolling areaEditorContainer → EditorSet the height on EditorContainer.
Fixed toolbar and scrolling content in one panelEditorFrame → toolbar + EditorContainer → EditorSet the total panel height on EditorFrame.

All three compositions render inside EditorRoot. EditorFrame is a copied UI layout component, not a platejs/react export. It supplies a flex column and can be replaced by your own layout with the same sizing behavior.

Minimal editor

Use variant="none" to omit the copied editor's document-sized padding and height styles. This editor grows with its content without either wrapper:

components/message-editor.tsx
import { EditorRoot, useCreateEditor } from 'platejs/react';
 
import { Editor } from '@/components/editor/editor';
 
export function MessageEditor() {
  const editor = useCreateEditor();
 
  return (
    <EditorRoot editor={editor}>
      <Editor
        aria-label="Message"
        className="min-h-24 rounded-md border p-3"
        placeholder="Write a message..."
        variant="none"
      />
    </EditorRoot>
  );
}
components/message-editor.tsx
import { EditorRoot, useCreateEditor } from 'platejs/react';
 
import { Editor } from '@/components/editor/editor';
 
export function MessageEditor() {
  const editor = useCreateEditor();
 
  return (
    <EditorRoot editor={editor}>
      <Editor
        aria-label="Message"
        className="min-h-24 rounded-md border p-3"
        placeholder="Write a message..."
        variant="none"
      />
    </EditorRoot>
  );
}

Scrollable editor

For a bounded editing area without a fixed toolbar, add EditorContainer and put the height directly on it. EditorFrame is unnecessary:

import { Editor, EditorContainer } from '@/components/editor/editor';
 
<EditorRoot editor={editor}>
  <EditorContainer className="h-48 rounded-md border">
    <Editor aria-label="Message" className="min-h-full p-3" variant="none" />
  </EditorContainer>
</EditorRoot>
import { Editor, EditorContainer } from '@/components/editor/editor';
 
<EditorRoot editor={editor}>
  <EditorContainer className="h-48 rounded-md border">
    <Editor aria-label="Message" className="min-h-full p-3" variant="none" />
  </EditorContainer>
</EditorRoot>

EditorContainer registers its div as the editor's scrolling element and renders the beforeContainer and afterContainer plugin slots as siblings. Keep it when an installed kit uses those slots, even if the editor does not need a fixed height. Omitting it also omits that UI. Without a registered container, scroll lookup falls back to the editor root or editable element.

Fixed toolbar and scrolling content

For a fixed toolbar and an internally scrolling editor, use EditorFrame to share a bounded height. The toolbar occupies its own row, and EditorContainer scrolls in the remaining space:

import {
  Editor,
  EditorContainer,
  EditorFrame,
} from '@/components/editor/editor';
 
<EditorRoot editor={editor}>
  <EditorFrame className="h-[650px]">
    <EditorContainer>
      <Editor />
    </EditorContainer>
  </EditorFrame>
</EditorRoot>
import {
  Editor,
  EditorContainer,
  EditorFrame,
} from '@/components/editor/editor';
 
<EditorRoot editor={editor}>
  <EditorFrame className="h-[650px]">
    <EditorContainer>
      <Editor />
    </EditorContainer>
  </EditorFrame>
</EditorRoot>

This composition assumes the editor includes FixedToolbarKit, which renders the toolbar through beforeContainer. For a manual toolbar, place FixedToolbar directly before EditorContainer inside the frame. Set an explicit frame height, or give its parent a definite height for h-full to fill. The frame does not impose a fixed height by itself.

Component map

ComponentUse For
EditorRootLifecycle and context for one mounted editor view.
EditorContentEditable Plate surface with plugin events, decorations, renderers, shortcuts, and editor effects.
EditorPreviewStatic read-only rendering with Plate fragment copy support.
EditorContainerEditor container div plus beforeContainer and afterContainer plugin slots.
NodeSelectionHighlightHighlight overlays for selected selectable blocks.
NodeSelectionDragBlank-space pointer-drag selection and its drag rectangle.
EditorElementDefault element renderer for block and inline elements.
EditorLeafDefault persisted-mark renderer for a text segment.
EditorTextDefault wrapper around every rendered segment of one text node.

Render pipeline

EditorContent composes its editable behavior from intrinsic surface props and installed plugins. Structural renderers and Decoration sources are plugin-owned. Compose the public components and plugin slots; EditorContent owns its internal lifecycle.

EditorContent composes slots.wrapRoot around each editable view and supplies its editableRef to every root slot. The ref is null before attachment and after detach; integrations read it after commit and clean up the element they captured. Feature kits use this boundary for required React integration.

StageSource
Editor contextEditorRoot provides the editor and plugin Decoration sources; EditorContent selects the content root.
Editable propsEditorContent pipes plugin DOM events and plugin-owned element, leaf, and text renderers.
Plugin slotsslots.wrapRoot, slots.wrapContent, slots.beforeEditable, and slots.afterEditable compose the editable surface.
LifecycleEditorContent automatically manages shortcuts, editor refs, controller registration, and cleanup.
Read-only statedisabled forces read-only; EditorContent can override the readOnly value supplied by EditorRoot.

Node primitives

Use EditorElement, EditorLeaf, and EditorText inside plugin components. They merge Plate attributes with your className, style, and ref.

components/paragraph-element.tsx
import {
  ParagraphPlugin,
  EditorElement,
  type EditorElementProps,
} from "platejs/react";
 
export function ParagraphElement(
  props: EditorElementProps<typeof ParagraphPlugin>
) {
  return <EditorElement as="p" className="leading-7" {...props} />;
}
components/paragraph-element.tsx
import {
  ParagraphPlugin,
  EditorElement,
  type EditorElementProps,
} from "platejs/react";
 
export function ParagraphElement(
  props: EditorElementProps<typeof ParagraphPlugin>
) {
  return <EditorElement as="p" className="leading-7" {...props} />;
}

Pass a plugin descriptor when the component belongs to one plugin. The props infer that plugin's configured element type.

components/quote-element.tsx
import { QuotePlugin } from "@/components/editor/quote-plugin";
import { EditorElement, type EditorElementProps } from "platejs/react";
 
export function QuoteElement(props: EditorElementProps<typeof QuotePlugin>) {
  return <EditorElement as="blockquote" {...props} />;
}
components/quote-element.tsx
import { QuotePlugin } from "@/components/editor/quote-plugin";
import { EditorElement, type EditorElementProps } from "platejs/react";
 
export function QuoteElement(props: EditorElementProps<typeof QuotePlugin>) {
  return <EditorElement as="blockquote" {...props} />;
}

Low-level renderer infrastructure that has no plugin owner uses the raw render contract instead:

import type { Element, RenderElementProps } from "platejs";
 
export function TypedElement<TElement extends Element>(
  props: RenderElementProps<TElement>
) {
  return <div {...props.attributes}>{props.children}</div>;
}
import type { Element, RenderElementProps } from "platejs";
 
export function TypedElement<TElement extends Element>(
  props: RenderElementProps<TElement>
) {
  return <div {...props.attributes}>{props.children}</div>;
}

When a schema element owns a named content root, render it through the typed slots.contentRoot(slot) function. The same component works in interactive and static rendering; Plate chooses an editable root view or static root children.

components/figure-element.tsx
import { FigurePlugin } from "@/components/editor/figure-plugin";
import type { EditorElementProps } from "platejs/react";
 
export function FigureElement({
  attributes,
  children,
  slots,
}: EditorElementProps<typeof FigurePlugin>) {
  return (
    <figure {...attributes}>
      {children}
      <figcaption>{slots.contentRoot("caption")}</figcaption>
    </figure>
  );
}
components/figure-element.tsx
import { FigurePlugin } from "@/components/editor/figure-plugin";
import type { EditorElementProps } from "platejs/react";
 
export function FigureElement({
  attributes,
  children,
  slots,
}: EditorElementProps<typeof FigurePlugin>) {
  return (
    <figure {...attributes}>
      {children}
      <figcaption>{slots.contentRoot("caption")}</figcaption>
    </figure>
  );
}
PrimitiveBehavior
EditorElementAdds data-editor-node="element", preserves Plate's data-editor-node-key, exposes typed element-owned content-root slots, and adds directional-affinity spacers when needed.
EditorLeafRenders persisted marks around a text segment and adds hard-affinity spacers when needed.
EditorTextRenders one text-node wrapper around all mark and Decoration segments.

Node selection

Plate stores node selection in the editor model. Plate React provides two independent DOM primitives for its visual presentation and pointer-drag input. Compose them as siblings after EditorContent under the same EditorRoot provider.

components/editor.tsx
import {
  NodeSelectionDrag,
  NodeSelectionHighlight,
  EditorContent,
  type EditorContentProps,
} from "platejs/react";
 
export function Editor(props: EditorContentProps) {
  return (
    <>
      <EditorContent {...props} />
      <NodeSelectionHighlight className="z-1 bg-brand/[.13]" />
      <NodeSelectionDrag className="z-50 border border-brand/25 bg-brand/15" />
    </>
  );
}
components/editor.tsx
import {
  NodeSelectionDrag,
  NodeSelectionHighlight,
  EditorContent,
  type EditorContentProps,
} from "platejs/react";
 
export function Editor(props: EditorContentProps) {
  return (
    <>
      <EditorContent {...props} />
      <NodeSelectionHighlight className="z-1 bg-brand/[.13]" />
      <NodeSelectionDrag className="z-50 border border-brand/25 bg-brand/15" />
    </>
  );
}

NodeSelectionHighlight portals a highlight into each selected block. NodeSelectionDrag renders the active drag rectangle in the editable document’s body and writes intersecting blocks through the editor's selection API. Selection candidates must pass editor.read.schema.isBlockContent(element) and editor.read.nodes.isSelectable(element).

Escape, pointer cancellation, window blur, and readonly transitions stop the current drag and discard pending movement. Previously published selection stays in the model. Shift anchors follow node identity across document changes.

Structural internals opt out with blockContent: false. A block that owns custom highlight geometry sets data-node-selection-highlight="self" and can read its exact state with useElementSelected({ mode: "node" }).

PrimitiveDOM contract
NodeSelectionHighlightAccepts div attributes except children; renders data-slot="node-selection-highlight" with required inset positioning.
NodeSelectionDragAccepts div attributes except children; renders data-slot="node-selection-drag" with required fixed positioning.

Use the Selection API for model reads and writes. See the Node Selection demo for the copied Editor composition.

API Reference

EditorRoot

Root provider for one editor instance.

Props

    Editor instance. When null, EditorRoot renders nothing.

    React children that can read the selected editor context.

    Sets the input intent and accepted, proposed, or markup projection for this mounted authored view. Editing can keep pending changes visible; changing the projection does not accept or reject them.

    Read-only state provided to content views. Defaults to editor.read.view.isReadOnly().

    Registers the editor as a primary editor for EditorController.

    Observes every published editor commit for the lifetime of the EditorRoot provider.

    Observes commits that change the serializable document. value contains primary children, named roots, and persisted meta.

    Observes commits that change the primary-root selection.

    Observes canonical node changes for the lifetime of the EditorRoot provider.

    Observes canonical text changes for the lifetime of the EditorRoot provider.

    Suppresses warnings about mounting the same editor instance more than once.

EditorContent

Editable surface for an EditorRoot editor.

Props

    Focuses the editor at the end when readOnly changes from true to false.

    Forces read-only state and sets aria-disabled.

    Overrides the read-only value supplied by EditorRoot. disabled always forces read-only.

    Placeholder content for the editable surface.

    Plate selection scrolling hook.

    DOM before-input handler passed through the plugin handler pipeline.

    Keyboard handler passed through the plugin handler pipeline.

    Passed to Plate Editable.

    ARIA role passed to Plate Editable.

    Style object passed to Plate Editable.

EditorContent also accepts the DOM handler props listed in DOMHandlers, including clipboard, composition, focus, keyboard, pointer, mouse, drag, touch, media, and form handlers.

EditorPreview

Read-only static renderer with Plate copy support.

Props

    Static editor instance.

    Overrides the default Plate fragment copy handler.

    Merged with the editor-editor class by EditorStatic.

    Style object passed to the static root div.

EditorContainer

Optional container div that registers the editor's scrolling element and renders plugin container slots. EditorContent works without it. The package component has no scrolling styles; apply them yourself or use the styled Editor registry item. See Editor layout for minimal, scrollable, and fixed-toolbar compositions.

Props

    Content rendered inside the container.

    HTML props passed to the container div. Sibling slots receive containerRef, not these props.

Render primitives

APIDefault ElementNotes
EditorElementdivAccepts as, attributes, className, style, ref, element, path, editor, plugin, and insetProp.
EditorLeafspanAccepts as, attributes, className, style, ref, leaf, text, editor, plugin, and inset.
EditorTextspanAccepts as, attributes, className, style, ref, text, editor, and plugin.