Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • HTML Export
  • Export
  • Server-Side
  • Version History
  • Editable Voids
  • Huge Document
  • Hundreds Editors
  • Markdown Streaming
  • Preview Markdown
  • Collaboration Demo
  • Table Nomerge Demo
  • Excalidraw Demo
  • Code Drawing Demo
  • Single Block Demo
  • Find Demo
  • Lint Demo
  • AI Demo
  • Align Demo
  • Autoformat Demo
  • Basic Nodes Demo
  • Block Menu Demo
  • Node Selection Demo
  • Column Demo
  • Code Block Demo
  • Callout Demo
  • Discussion Demo
  • Date Demo
  • Footnote Demo
  • Drag & Drop Demo
  • Emoji Demo
  • Equation Demo
  • Exit Break Demo
  • Floating Toolbar Demo
  • Font Demo
  • Indent Demo
  • List Demo
  • Line Height Demo
  • Link Demo
  • Media Demo
  • Mention Demo
  • Block Placeholder Demo
  • Serializing Docx Demo
  • Serializing HTML Demo
  • Serializing Markdown Demo
  • Slash Command Demo
  • Plugin Rules Demo
  • Table Demo
  • Table of Contents Demo
  • Details Demo

Editable Voids

PreviousNext

Nest editable controls and a Plate editor inside a void element.

This example renders form controls and a nested Plate editor inside a void element. It is app-local demo code built with definePlugin, not a packaged feature plugin.

Demo

Loading…
Version HistoryHuge Document

On This Page

DemoSourceRuntime shapeVoid element rulesRelated
Build your editor
Production-ready AI template and reusable components.
Get all-access

Source

The demo registers an editable-void element plugin and renders custom React UI for that node.

'use client';
 
import {
  type EditorElementProps,
  definePlugin,
  EditorRoot,
  EditorElement,
  useCreateEditor,
} from 'platejs/react';
import * as React from 'react';
 
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import {
  Editor,
  EditorContainer,
  EditorFrame,
} from '@/components/editor/editor';
import { EditorKit } from '@/components/editor/plugins';
import { editableVoidsValue } from '@/registry/examples/values/editable-voids-value';
 
export const EditableVoidPlugin = definePlugin('editableVoid', {
  schema: { element: { void: 'block' } },
});
 
export function EditableVoidElement({
  children,
  ...props
}: EditorElementProps<typeof EditableVoidPlugin>) {
  const [inputValue, setInputValue] = React.useState('');
 
  const editor = useCreateEditor({
    plugins: EditorKit,
  });
 
  return (
    // Need contentEditable=false or Firefox has issues with certain input types.
    <EditorElement
      {...props}
      attributes={{ ...props.attributes, contentEditable: false }}
    >
      <div className="mt-2 grid gap-6 rounded-md border p-6 shadow-sm">
        <Input
          id="name"
          className="my-2"
          value={inputValue}
          onChange={(e) => {
            setInputValue(e.target.value);
          }}
          placeholder="Name"
          type="text"
        />
 
        <div className="grid w-full max-w-sm items-center gap-2">
          <Label htmlFor="handed">Left or right handed:</Label>
 
          <RadioGroup id="handed" defaultValue="r1">
            <div className="flex items-center space-x-2">
              <RadioGroupItem id="r1" value="r1" />
              <Label htmlFor="r1">Left</Label>
            </div>
            <div className="flex items-center space-x-2">
              <RadioGroupItem id="r2" value="r2" />
              <Label htmlFor="r2">Right</Label>
            </div>
          </RadioGroup>
        </div>
 
        <div className="grid gap-2">
          <Label htmlFor="editable-void-basic-blocks">
            Tell us about yourself:
          </Label>
 
          <EditorRoot
            editor={editor}
            // initialValue={basicBlocksValue}
          >
            <EditorFrame className="h-auto">
              <EditorContainer>
                <Editor />
              </EditorContainer>
            </EditorFrame>
          </EditorRoot>
        </div>
      </div>
      {children}
    </EditorElement>
  );
}
 
export default function EditableVoidsDemo() {
  const editor = useCreateEditor({
    plugins: [
      ...EditorKit,
      EditableVoidPlugin.configure({ component: EditableVoidElement }),
    ],
    initialValue: editableVoidsValue,
  });
 
  return (
    <EditorRoot editor={editor}>
      <EditorFrame>
        <EditorContainer>
          <Editor />
        </EditorContainer>
      </EditorFrame>
    </EditorRoot>
  );
}
'use client';
 
import {
  type EditorElementProps,
  definePlugin,
  EditorRoot,
  EditorElement,
  useCreateEditor,
} from 'platejs/react';
import * as React from 'react';
 
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import {
  Editor,
  EditorContainer,
  EditorFrame,
} from '@/components/editor/editor';
import { EditorKit } 

























































































The initial value contains a normal paragraph, one editable-void element, and an empty paragraph after it.

/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test';
import type { Value } from 'platejs';
 
jsx;
 
export const editableVoidsValue: Value = (
  <fragment>
    <hp>
      In addition to nodes that contain editable text, you can insert void
      nodes, which can also contain editable elements, inputs, or an entire
      other Plate editor.
    </hp>
    <element type="editableVoid">
      <htext />
    </element>
    <hp>
      <htext />
    </hp>
  </fragment>
);
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test';
import type { Value } from 'platejs';
 
jsx;
 
export const editableVoidsValue: Value = (
  <fragment>
    <hp>
      In addition to nodes that contain editable text, you can insert void
      nodes, which can also contain editable elements, inputs, or an entire
      other Plate editor.
    </hp>
    <element type="editableVoid">
      <htext />
    </element>




Runtime shape

PieceOwnerNotes
EditableVoidPluginRegistry exampleUses definePlugin with schema.element.void: 'block'.
EditableVoidElementRegistry exampleRenders inputs, radio controls, and a nested <EditorRoot> instance.
Outer editorRegistry exampleLoads the authored editor plugins plus EditableVoidPlugin and the editableVoidsValue.
Inner editorRegistry exampleCreates a separate useCreateEditor({ plugins }) inside the void element.
Value shapePlateKeeps an empty text child inside the void node so Plate can select it.

Void element rules

Set contentEditable={false} on the custom void wrapper. Without it, browser editing behavior can leak into the nested controls; the demo notes Firefox input issues specifically.

Render {children} after the non-editable UI. Plate still needs the hidden void child mounted even when your visible element is fully custom React.

The nested editor is a separate Plate editor. It does not share the outer editor's value, selection, plugins, or undo history. Its own history is not reachable from the keyboard. Undo and Redo shortcuts pressed inside it run the outer editor's history, as Embedded widgets describes.

Native controls and a nested editor need no extra marker. A widget built from other elements, such as a canvas, needs the data-editor-root-chrome-ignore wrapper from that section.

Related

  • Plate Plugin covers schema.element.void, .configure({ component }), and plugin rendering.
  • Plugin Components covers custom node components.
  • Plate Components covers <EditorRoot>, editor providers, and node primitives.
from
'@/components/editor/plugins'
;
import { editableVoidsValue } from '@/registry/examples/values/editable-voids-value';
export const EditableVoidPlugin = definePlugin('editableVoid', {
schema: { element: { void: 'block' } },
});
export function EditableVoidElement({
children,
...props
}: EditorElementProps<typeof EditableVoidPlugin>) {
const [inputValue, setInputValue] = React.useState('');
const editor = useCreateEditor({
plugins: EditorKit,
});
return (
// Need contentEditable=false or Firefox has issues with certain input types.
<EditorElement
{...props}
attributes={{ ...props.attributes, contentEditable: false }}
>
<div className="mt-2 grid gap-6 rounded-md border p-6 shadow-sm">
<Input
id="name"
className="my-2"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Name"
type="text"
/>
<div className="grid w-full max-w-sm items-center gap-2">
<Label htmlFor="handed">Left or right handed:</Label>
<RadioGroup id="handed" defaultValue="r1">
<div className="flex items-center space-x-2">
<RadioGroupItem id="r1" value="r1" />
<Label htmlFor="r1">Left</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem id="r2" value="r2" />
<Label htmlFor="r2">Right</Label>
</div>
</RadioGroup>
</div>
<div className="grid gap-2">
<Label htmlFor="editable-void-basic-blocks">
Tell us about yourself:
</Label>
<EditorRoot
editor={editor}
// initialValue={basicBlocksValue}
>
<EditorFrame className="h-auto">
<EditorContainer>
<Editor />
</EditorContainer>
</EditorFrame>
</EditorRoot>
</div>
</div>
{children}
</EditorElement>
);
}
export default function EditableVoidsDemo() {
const editor = useCreateEditor({
plugins: [
...EditorKit,
EditableVoidPlugin.configure({ component: EditableVoidElement }),
],
initialValue: editableVoidsValue,
});
return (
<EditorRoot editor={editor}>
<EditorFrame>
<EditorContainer>
<Editor />
</EditorContainer>
</EditorFrame>
</EditorRoot>
);
}
<
hp
>
<htext />
</hp>
</fragment>
);