Overview
The Yoopta Editor is the main component that orchestrates all plugins, marks, and UI components. You create an editor instance with createYooptaEditor() (passing plugins and optional marks/value there), then render <YooptaEditor editor={editor} onChange={...} />. Content is stored on the editor instance and synced via onChange.
Creating an Editor Instance
Use createYooptaEditor() with an options object. Plugins are required; marks and initial value are optional:
import { createYooptaEditor } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';
const editor = useMemo(
() =>
createYooptaEditor({
plugins: [Paragraph],
marks: [], // optional, e.g. from @yoopta/marks
value: undefined, // optional initial content
id: undefined, // optional editor ID
readOnly: false,
}),
[],
);
Always wrap createYooptaEditor() in useMemo (with stable plugins/marks) to prevent recreating the editor on every render.
Plugins and marks are configured at creation time, not on the <YooptaEditor> component. Value is managed via the editor instance and onChange.
YooptaEditor Component Props
The <YooptaEditor> component accepts:
Required
The editor instance from createYooptaEditor()
Optional
onChange
(value: YooptaContentValue, options: YooptaOnChangeOptions) => void
Called when content changes. Use it to sync value to your state (e.g. for saving or controlled usage).
onPathChange
(path: YooptaPath) => void
Called when the selection path changes (current block, selected blocks, etc.).
Whether to focus the editor on mount.
Global placeholder text shown when the editor has a single empty block. If a plugin defines its own element-level placeholder, the element-level placeholder takes priority in multi-block editors. See Placeholders for details.
CSS class for the editor wrapper.
Inline styles for the editor wrapper.
UI components to render inside the editor (e.g. toolbar, slash menu, block actions).
renderBlock
(props: RenderBlockProps) => ReactNode
Custom wrapper for each block (e.g. for drag-and-drop).
Basic Example
import { useMemo, useState } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';
const plugins = [Paragraph];
export default function MyEditor() {
const editor = useMemo(
() =>
createYooptaEditor({
plugins,
value: undefined,
}),
[],
);
const [value, setValue] = useState(editor.children);
const handleChange = (newValue, options) => {
setValue(newValue);
console.log('Operations:', options.operations);
};
return (
<YooptaEditor
editor={editor}
onChange={handleChange}
placeholder="Start typing..."
autoFocus
/>
);
}
Placeholders
Yoopta supports placeholders at two levels that work together:
- Editor-level — a global fallback placeholder passed to
<YooptaEditor placeholder="..." />
- Element-level — per-plugin placeholders set via
.extend() on each element type
How resolution works
When a block is focused and its element is empty, the editor resolves which placeholder to display:
- Check the focused element’s type (e.g.
heading-one, step-list-item-heading)
- If that element has a
placeholder defined — use it
- Otherwise, fall back to the editor-level
placeholder prop
This means element-level placeholders always take priority over the global one.
Editor-level placeholder
<YooptaEditor
editor={editor}
placeholder="Type / to open menu..."
/>
Element-level placeholder
Set placeholders per element type using .extend():
import { HeadingOne } from '@yoopta/headings';
import Paragraph from '@yoopta/paragraph';
const plugins = [
Paragraph.extend({
elements: {
paragraph: {
placeholder: 'Type something...',
},
},
}),
HeadingOne.extend({
elements: {
'heading-one': {
placeholder: 'Heading 1',
},
},
}),
];
Nested plugins
For plugins with multiple nested elements (e.g. Steps, Accordion), you can set a different placeholder for each element:
import Steps from '@yoopta/steps';
const plugins = [
Steps.extend({
elements: {
'step-list-item-heading': {
placeholder: 'Step title',
},
'step-list-item-content': {
placeholder: 'Describe this step...',
},
},
}),
];
Each nested element resolves its own placeholder independently — the heading shows “Step title” while the content area shows “Describe this step…”.
Styling
Placeholders are rendered via the .yoopta-placeholder CSS class and data-placeholder attribute. If you use a Yoopta theme (e.g. @yoopta/themes-shadcn), placeholder styles are included. For headless usage, add your own styles:
.yoopta-placeholder {
position: relative;
}
.yoopta-placeholder::before {
content: attr(data-placeholder);
position: absolute;
top: 0;
left: 0;
pointer-events: none;
opacity: 0.4;
white-space: nowrap;
user-select: none;
}
Content Value Structure
Content is a record of block ID → block data. Type names use PascalCase for block types (e.g. Paragraph), and kebab-case for element types inside value.
type YooptaContentValue = Record<string, YooptaBlockData>;
type YooptaBlockData = {
id: string;
type: string; // e.g. 'Paragraph', 'HeadingOne'
meta: {
order: number;
depth: number;
align?: 'left' | 'center' | 'right';
};
value: SlateElement[]; // Slate nodes (paragraph, heading-one, etc.)
};
Example
{
"block-1": {
"id": "block-1",
"type": "Paragraph",
"meta": { "order": 0, "depth": 0 },
"value": [
{
"id": "el-1",
"type": "paragraph",
"children": [{ "text": "Hello World!" }]
}
]
}
}
onChange Options
The onChange callback receives the new content and an options object:
type YooptaOnChangeOptions = {
operations: YooptaOperation[]; // List of operations that caused the change
};
Use options.operations to know what changed (insert, update, delete, move, etc.) without diffing the whole value.
Styling the Editor
Using className
<YooptaEditor editor={editor} onChange={onChange} className="my-editor" />
.my-editor {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
min-height: 500px;
}
Using style
<YooptaEditor
editor={editor}
onChange={onChange}
style={{
maxWidth: '800px',
margin: '0 auto',
padding: '40px 20px',
minHeight: '500px',
}}
/>
Multiple Editors
Each editor instance is independent. Create separate instances with createYooptaEditor() and separate state for each:
function MultipleEditors() {
const editor1 = useMemo(() => createYooptaEditor({ plugins }), []);
const editor2 = useMemo(() => createYooptaEditor({ plugins }), []);
const [value1, setValue1] = useState(editor1.children);
const [value2, setValue2] = useState(editor2.children);
return (
<>
<YooptaEditor editor={editor1} onChange={setValue1} />
<YooptaEditor editor={editor2} onChange={setValue2} />
</>
);
}
Next Steps