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.
EditorRoot owns one mounted editor view and provides its context. EditorContent renders the editable surface under that view.
import { EditorRoot, EditorContent, useCreateEditor } from "platejs/react";
export function
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>
);
}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.
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 value | Behavior |
|---|---|
{ 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.
Use EditorPreview with a static editor when you need rendered content and Plate copy behavior without an editable surface.
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} />;
}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.
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.
| Layout | Composition | Where to set the height |
|---|---|---|
| Auto-growing, textarea-like editor | Editor | Set a minimum height on Editor. |
| Editor with its own scrolling area | EditorContainer → Editor | Set the height on EditorContainer. |
| Fixed toolbar and scrolling content in one panel | EditorFrame → toolbar + EditorContainer → Editor | Set 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.
Use variant="none" to omit the copied editor's document-sized padding and
height styles. This editor grows with its content without either wrapper:
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>
);
}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>
);
}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.
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 | Use For |
|---|---|
EditorRoot | Lifecycle and context for one mounted editor view. |
EditorContent | Editable Plate surface with plugin events, decorations, renderers, shortcuts, and editor effects. |
EditorPreview | Static read-only rendering with Plate fragment copy support. |
EditorContainer | Editor container div plus beforeContainer and afterContainer plugin slots. |
NodeSelectionHighlight | Highlight overlays for selected selectable blocks. |
NodeSelectionDrag | Blank-space pointer-drag selection and its drag rectangle. |
EditorElement | Default element renderer for block and inline elements. |
EditorLeaf | Default persisted-mark renderer for a text segment. |
EditorText | Default wrapper around every rendered segment of one text node. |
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.
| Stage | Source |
|---|---|
| Editor context | EditorRoot provides the editor and plugin Decoration sources; EditorContent selects the content root. |
| Editable props | EditorContent pipes plugin DOM events and plugin-owned element, leaf, and text renderers. |
| Plugin slots | slots.wrapRoot, slots.wrapContent, slots.beforeEditable, and slots.afterEditable compose the editable surface. |
| Lifecycle | EditorContent automatically manages shortcuts, editor refs, controller registration, and cleanup. |
| Read-only state | disabled forces read-only; EditorContent can override the readOnly value supplied by EditorRoot. |
Use EditorElement, EditorLeaf, and EditorText inside plugin components. They merge Plate attributes with your className, style, and ref.
import {
ParagraphPlugin,
EditorElement,
type EditorElementProps,
} from "platejs/react";
export function ParagraphElement(
props: EditorElementProps<typeof ParagraphPlugin>
) {
return <EditorElement as="p" className="leading-7" {...props} />;
}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.
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} />;
}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.
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>
);
}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>
);
}| Primitive | Behavior |
|---|---|
EditorElement | Adds 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. |
EditorLeaf | Renders persisted marks around a text segment and adds hard-affinity spacers when needed. |
EditorText | Renders one text-node wrapper around all mark and Decoration segments. |
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.
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" />
</>
);
}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" }).
| Primitive | DOM contract |
|---|---|
NodeSelectionHighlight | Accepts div attributes except children; renders data-slot="node-selection-highlight" with required inset positioning. |
NodeSelectionDrag | Accepts 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.
Root provider for one editor instance.
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.
Editable surface for an EditorRoot editor.
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.
Read-only static renderer with Plate copy support.
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.
| API | Default Element | Notes |
|---|---|---|
EditorElement | div | Accepts as, attributes, className, style, ref, element, path, editor, plugin, and insetProp. |
EditorLeaf | span | Accepts as, attributes, className, style, ref, leaf, text, editor, plugin, and inset. |
EditorText | span | Accepts as, attributes, className, style, ref, text, editor, and plugin. |