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
  • 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 CSV 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="editable-void">
      <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="editable-void">
      <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.

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