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>
| Piece | Owner | Notes |
|---|---|---|
EditableVoidPlugin | Registry example | Uses definePlugin with schema.element.void: 'block'. |
EditableVoidElement | Registry example | Renders inputs, radio controls, and a nested <EditorRoot> instance. |
| Outer editor | Registry example | Loads the authored editor plugins plus EditableVoidPlugin and the editableVoidsValue. |
| Inner editor | Registry example | Creates a separate useCreateEditor({ plugins }) inside the void element. |
| Value shape | Plate | Keeps an empty text child inside the void node so Plate can select it. |
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.
schema.element.void,
.configure({ component }), and plugin rendering.<EditorRoot>, editor providers, and node primitives.