From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Examples
    • Plate to HTML
    • 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
    • List Classic Demo
    • Find Replace 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
    • Cursor Overlay 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
    • Toggle 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 definePlatePlugin, 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 { PlateElementProps } from 'platejs/react';
import {
  definePlatePlugin,
  Plate,
  PlateElement,
  usePlateEditor,
} 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 } from '@/components/editor/editor';
import { EditorKit } from '@/components/editor/plugins';
import { editableVoidsValue } from '@/registry/examples/values/editable-voids-value';
 
export const EditableVoidPlugin = definePlatePlugin('editableVoid', {
  schema: { element: { void: 'editable-island' } },
});
 
export function EditableVoidElement({
  children,
  ...props
}: PlateElementProps<typeof EditableVoidPlugin>) {
  const [inputValue, setInputValue] = React.useState('');
 
  const editor = usePlateEditor({
    plugins: EditorKit,
  });
 
  return (
    // Need contentEditable=false or Firefox has issues with certain input types.
    <PlateElement
      {...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>
 
          <Plate
            editor={editor}
            // initialValue={basicBlocksValue}
          >
            <EditorContainer>
              <Editor />
            </EditorContainer>
          </Plate>
        </div>
      </div>
      {children}
    </PlateElement>
  );
}
 
export default function EditableVoidsDemo() {
  const editor = usePlateEditor({
    plugins: [
      ...EditorKit,
      EditableVoidPlugin.configure({ component: EditableVoidElement }),
    ],
    initialValue: editableVoidsValue,
  });
 
  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor />
      </EditorContainer>
    </Plate>
  );
}
'use client';
 
import type { PlateElementProps } from 'platejs/react';
import {
  definePlatePlugin,
  Plate,
  PlateElement,
  usePlateEditor,
} 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 } from '@/components/editor/editor';
import { EditorKit } from '@/components/editor/plugins';





















































































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-utils';
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-utils';
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 definePlatePlugin with schema.element.void: 'editable-island'.
EditableVoidElementRegistry exampleRenders inputs, radio controls, and a nested <Plate> instance.
Outer editorRegistry exampleLoads the authored editor plugins plus EditableVoidPlugin and the editableVoidsValue.
Inner editorRegistry exampleCreates a separate usePlateEditor({ plugins }) inside the void element.
Value shapePliteKeeps an empty text child inside the void node so Plite 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. Plite 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 <Plate>, editor providers, and node primitives.
import
{ editableVoidsValue }
from
'@/registry/examples/values/editable-voids-value'
;
export const EditableVoidPlugin = definePlatePlugin('editableVoid', {
schema: { element: { void: 'editable-island' } },
});
export function EditableVoidElement({
children,
...props
}: PlateElementProps<typeof EditableVoidPlugin>) {
const [inputValue, setInputValue] = React.useState('');
const editor = usePlateEditor({
plugins: EditorKit,
});
return (
// Need contentEditable=false or Firefox has issues with certain input types.
<PlateElement
{...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>
<Plate
editor={editor}
// initialValue={basicBlocksValue}
>
<EditorContainer>
<Editor />
</EditorContainer>
</Plate>
</div>
</div>
{children}
</PlateElement>
);
}
export default function EditableVoidsDemo() {
const editor = usePlateEditor({
plugins: [
...EditorKit,
EditableVoidPlugin.configure({ component: EditableVoidElement }),
],
initialValue: editableVoidsValue,
});
return (
<Plate editor={editor}>
<EditorContainer>
<Editor />
</EditorContainer>
</Plate>
);
}
hp
>
<htext />
</hp>
</fragment>
);