# Blocks API
Source: https://docs.yoopta.dev/api-reference/blocks
Block-level operations for Yoopta Editor
# Blocks API
The Blocks API provides block-level operations: creation, deletion, movement, transformation, and navigation. It works above the Elements API, operating on entire blocks rather than elements within blocks.
## Accessing the API
Import the `Blocks` namespace from `@yoopta/editor`:
```typescript theme={null}
import { Blocks } from '@yoopta/editor';
Blocks.insertBlock(editor, 'Paragraph', { at: 0 });
Blocks.deleteBlock(editor, { at: 3 });
Blocks.moveBlock(editor, 'block-id', 5);
```
Blocks API methods are not on the editor instance — use the `Blocks` namespace.
## Type Definitions
```typescript theme={null}
type YooptaPathIndex = number;
type YooptaBlockData = {
id: string;
type: string;
value: SlateElement[];
meta: {
order: number;
depth: number;
align?: 'left' | 'center' | 'right';
};
};
```
***
## Methods
### insertBlock
Creates and inserts a new block of the given type.
```typescript theme={null}
Blocks.insertBlock(
editor: YooEditor,
type: string,
options?: InsertBlockOptions
): string
```
**Parameters:**
* `editor` — Editor instance (required).
* `type` — Block type, e.g. `'Paragraph'`, `'Heading'` (required).
* `options.at` — Position to insert; default: `editor.path.current`.
* `options.focus` — Whether to focus the block after insertion.
* `options.blockData` — Custom block data (id, meta, value).
* `options.elements` — Custom element structure via `editor.y()`.
**Returns:** ID of the new block.
```typescript theme={null}
const blockId = Blocks.insertBlock(editor, 'Paragraph', {
at: editor.path.current,
focus: true,
});
```
***
### deleteBlock
Removes a block and optionally focuses another.
```typescript theme={null}
Blocks.deleteBlock(editor: YooEditor, options?: DeleteBlockOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the block to delete.
* `options.blockId` — ID of the block to delete (overrides `at` if both set).
* `options.focus` — Whether to focus a block after deletion; default: `true`.
* `options.focusTarget` — Which block to focus: `'previous'` | `'next'` | `'none'`; default: `'previous'`.
```typescript theme={null}
Blocks.deleteBlock(editor, { at: 3, focusTarget: 'next' });
```
***
### updateBlock
Updates a block's metadata and/or content.
```typescript theme={null}
Blocks.updateBlock(
editor: YooEditor,
blockId: string,
newData: Omit, 'id' | 'type'>
): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `blockId` — ID of the block to update (required).
* `newData.meta` — Metadata to update: `order`, `depth`, `align`.
* `newData.value` — New block content (array of Slate elements).
```typescript theme={null}
Blocks.updateBlock(editor, 'block-123', {
meta: { align: 'center' },
});
```
***
### getBlock
Retrieves block data by ID or path.
```typescript theme={null}
Blocks.getBlock(editor: YooEditor, options: GetBlockOptions): YooptaBlockData | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position index of the block.
* `options.id` — ID of the block (overrides `at` if both set). One of `at` or `id` is required.
**Returns:** Block data or `null`.
```typescript theme={null}
const block = Blocks.getBlock(editor, { id: 'block-123' });
```
***
### moveBlock
Moves a block to a new position.
```typescript theme={null}
Blocks.moveBlock(
editor: YooEditor,
draggedBlockId: string,
newPath: number
): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `draggedBlockId` — ID of the block to move (required).
* `newPath` — New position (order index) for the block (required).
```typescript theme={null}
Blocks.moveBlock(editor, 'block-123', 5);
```
***
### focusBlock
Focuses a block and optionally sets cursor position.
```typescript theme={null}
Blocks.focusBlock(
editor: YooEditor,
blockId: string,
options?: FocusBlockOptions
): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `blockId` — ID of the block to focus (required).
* `options.waitExecution` — Delay focus (e.g. until DOM is ready).
* `options.waitExecutionMs` — Delay in milliseconds.
* `options.shouldUpdateBlockPath` — Update `editor.path.current`; default: `true`.
* `options.focusAt` — Cursor position: `Point` or `Path`; default: first node.
* `options.slate` — Slate editor instance to use (optional).
```typescript theme={null}
Blocks.focusBlock(editor, 'block-123', { focusAt: { path: [0, 0], offset: 5 } });
```
***
### duplicateBlock
Creates a copy of a block at a given position.
```typescript theme={null}
Blocks.duplicateBlock(
editor: YooEditor,
options?: DuplicateBlockOptions
): string | undefined
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the block to duplicate; default: `editor.path.current`.
* `options.blockId` — ID of the block to duplicate (overrides `at`).
* `options.insertAt` — Position to insert the duplicate; default: after original.
* `options.focus` — Focus the new block after creation; default: `true`.
* `options.elements` — Custom element structure for the duplicate.
**Returns:** New block ID or `undefined`.
```typescript theme={null}
const newId = Blocks.duplicateBlock(editor, { at: 2, insertAt: 4 });
```
***
### splitBlock
Splits a block at the selection (or given position).
```typescript theme={null}
Blocks.splitBlock(
editor: YooEditor,
options?: SplitBlockOptions
): string | undefined
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the block to split; default: `editor.path.current`.
* `options.blockId` — ID of the block to split.
* `options.splitAt` — Location to split at; default: current selection.
* `options.focus` — Focus after split; default: `true`.
* `options.focusTarget` — Which block to focus: `'new'` | `'original'` | `'none'`; default: `'new'`.
* `options.preserveContent` — Keep content in both blocks; default: `true`.
**Returns:** New block ID or `undefined`.
```typescript theme={null}
const newBlockId = Blocks.splitBlock(editor, { focusTarget: 'new' });
```
***
### mergeBlock
Merges a block into another (default: into previous).
```typescript theme={null}
Blocks.mergeBlock(editor: YooEditor, options?: MergeBlockOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the source block (the one merged into target).
* `options.blockId` — ID of the source block.
* `options.targetAt` — Position of the target block (receives content); default: previous block.
* `options.targetBlockId` — ID of the target block.
* `options.focus` — Focus the target block after merge; default: `true`.
* `options.preserveContent` — Keep content from source block; default: `true`.
```typescript theme={null}
Blocks.mergeBlock(editor, { at: 5, targetAt: 3 });
```
***
### toggleBlock
Transforms block type (e.g. Paragraph → Heading) or inserts element in leaf.
```typescript theme={null}
Blocks.toggleBlock(
editor: YooEditor,
type: string,
options?: ToggleBlockOptions
): string
```
**Parameters:**
* `editor` — Editor instance (required).
* `type` — Target block/element type, e.g. `'Heading'`, `'Paragraph'` (required).
* `options.at` — Position of the block; default: `editor.path.current`.
* `options.scope` — `'auto'` (default) | `'block'` (transform block) | `'element'` (insert in leaf).
* `options.preserveContent` — Transfer text to new block/element; default: `true`.
* `options.focus` — Focus after toggle; default: `false`.
* `options.elements` — Custom element structure via `editor.y()`.
**Returns:** Toggled block ID.
```typescript theme={null}
Blocks.toggleBlock(editor, 'Heading', { preserveContent: true });
```
***
### increaseBlockDepth
Increases block depth (indent).
```typescript theme={null}
Blocks.increaseBlockDepth(
editor: YooEditor,
options?: BlockDepthOptions
): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the block; default: `editor.path.current`.
* `options.blockId` — ID of the block (overrides `at`).
```typescript theme={null}
Blocks.increaseBlockDepth(editor, { at: editor.path.current });
```
***
### decreaseBlockDepth
Decreases block depth (outdent).
```typescript theme={null}
Blocks.decreaseBlockDepth(
editor: YooEditor,
options?: BlockDepthOptions
): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the block; default: `editor.path.current`.
* `options.blockId` — ID of the block (overrides `at`).
```typescript theme={null}
Blocks.decreaseBlockDepth(editor, { blockId: 'block-123' });
```
***
### getBlockSlate
Returns the Slate editor instance for a block.
```typescript theme={null}
Blocks.getBlockSlate(
editor: YooEditor,
options: GetBlockSlateOptions
): SlateEditor | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Position of the block.
* `options.id` — ID of the block (overrides `at`). One of `at` or `id` is required.
**Returns:** Slate editor or `null`.
```typescript theme={null}
const slate = Blocks.getBlockSlate(editor, { id: 'block-123' });
if (slate) {
const text = Editor.string(slate, []);
}
```
***
### buildBlockData
Builds a `YooptaBlockData` structure (does not insert into editor).
```typescript theme={null}
Blocks.buildBlockData(block?: BuildBlockDataOptions): YooptaBlockData
```
**Parameters:**
* `block.id` — Block ID; if omitted, a unique ID is generated.
* `block.type` — Block type (plugin name); default: `'Paragraph'`.
* `block.value` — Block content (array of Slate elements).
* `block.meta` — Metadata: `order`, `depth`, `align`.
**Returns:** Complete `YooptaBlockData` object.
```typescript theme={null}
const blockData = Blocks.buildBlockData({
type: 'Heading',
meta: { order: 0, depth: 0, align: 'center' },
});
```
# Editor API
Source: https://docs.yoopta.dev/api-reference/editor
createYooptaEditor and YooEditor instance API
# Editor API
The Editor API is the entry point for Yoopta: `createYooptaEditor` creates an editor instance (`YooEditor`) that holds content, block/element methods, events, parsers, and history. Use it together with the [Blocks](/api-reference/blocks), [Elements](/api-reference/elements), and [Marks](/api-reference/marks) APIs.
## createYooptaEditor
Creates a Yoopta editor instance.
```typescript theme={null}
createYooptaEditor(options: CreateYooptaEditorOptions): YooEditor
```
**Parameters:**
* `options.plugins` — Array of block plugins (required).
* `options.marks` — Optional array of mark definitions (e.g. from `createYooptaMark`).
* `options.value` — Optional initial content (`YooptaContentValue`). If invalid or omitted, one empty paragraph block is used.
* `options.id` — Optional editor ID; a unique ID is generated if omitted.
* `options.readOnly` — Optional; default: `false`.
**Returns:** `YooEditor` instance.
```typescript theme={null}
import { createYooptaEditor } from '@yoopta/editor';
import { ParagraphPlugin } from '@yoopta/paragraph';
const editor = createYooptaEditor({
plugins: [ParagraphPlugin],
marks: [],
value: undefined,
readOnly: false,
});
```
***
## Type Definitions
```typescript theme={null}
type YooptaContentValue = Record;
type YooptaPath = {
current: number | null;
selected?: number[] | null;
selection?: Selection | null;
source?: 'selection-box' | 'native-selection' | 'mousemove' | 'keyboard' | 'copy-paste' | null;
};
type CreateYooptaEditorOptions = {
id?: string;
plugins: readonly YooptaPlugin[];
marks?: YooptaMark[];
value?: YooptaContentValue;
readOnly?: boolean;
};
```
***
## YooEditor instance
The object returned by `createYooptaEditor` exposes the following. Block and element methods are documented in [Blocks API](/api-reference/blocks) and [Elements API](/api-reference/elements).
### Content
* **`editor.children`** — Current content: `YooptaContentValue` (record of block id → block data).
* **`editor.isEmpty()`** — Returns whether the editor has no meaningful content.
* **`editor.getEditorValue()`** — Returns `editor.children` (current content).
* **`editor.setEditorValue(value)`** — Replaces editor content with `value`; if `value` is `null` or invalid, resets to one empty paragraph block.
**Parameters for setEditorValue:**
* `value` — `YooptaContentValue | null`; use `null` or invalid value to reset.
***
### Block methods (on editor)
Available on the instance; see [Blocks API](/api-reference/blocks) for full signatures and options.
* `insertBlock`, `deleteBlock`, `updateBlock`, `getBlock`
* `moveBlock`, `focusBlock`, `duplicateBlock`
* `toggleBlock`, `splitBlock`, `mergeBlock`
* `increaseBlockDepth`, `decreaseBlockDepth`
* `setPath(path)` — Sets `editor.path` (e.g. current block index). `path: YooptaPath`.
***
### Element methods (on editor)
Available on the instance; see [Elements API](/api-reference/elements) for full signatures and options.
* `insertElement`, `updateElement`, `deleteElement`
* `getElement`, `getElements`, `getElementEntry`
* `getElementPath`, `getParentElementPath`, `getElementRect`
* `getElementChildren`, `getRootElement`, `isElementEmpty`
***
### Element builder: editor.y
Build Slate elements for use in block/element APIs.
```typescript theme={null}
editor.y(type: string, options?: ElementStructureOptions): SlateElement
editor.y.text(text: string, marks?: TextNodeOptions): SlateElementTextNode
editor.y.inline(type: string, options?: ElementStructureOptions): SlateElement
```
* **`editor.y(type, options)`** — Block element: `type` (e.g. `'paragraph'`, `'heading-one'`), optional `options.props`, `options.children`.
* **`editor.y.text(text, marks)`** — Text node with optional marks (e.g. `{ bold: true }`).
* **`editor.y.inline(type, options)`** — Inline element (e.g. `'link'` with `props.url`).
***
### Path
* **`editor.path`** — Current path state: `{ current, selected?, selection?, source? }`.
* **`editor.setPath(path)`** — Updates path; e.g. `editor.setPath({ current: 2 })`.
***
### Focus
* **`editor.focus()`** — Focuses the editor.
* **`editor.blur(options?)`** — Removes focus.
* **`editor.isFocused()`** — Returns whether the editor is focused.
***
### Events
* **`editor.on(event, callback)`** — Subscribe to event.
* **`editor.off(event, callback)`** — Unsubscribe.
* **`editor.once(event, callback)`** — Subscribe once.
* **`editor.emit(event, payload)`** — Emit event.
**Events:** `'change'` (payload: `{ operations, value }`), `'focus'`, `'blur'`, `'block:copy'`, `'path-change'`, `'plugin:register'` (payload: `{ type }`), `'plugin:unregister'` (payload: `{ type }`).
***
### Parsers (serialization)
Convert content to different formats. Each parser takes the content to serialize (usually `editor.children` or the value from `onChange`).
```typescript theme={null}
editor.getHTML(content: YooptaContentValue): string
editor.getMarkdown(content: YooptaContentValue): string
editor.getPlainText(content: YooptaContentValue): string
editor.getEmail(content: YooptaContentValue, options?: Partial): string
editor.getYooptaJSON(content: YooptaContentValue): string
```
**Parameters:**
* `content` — `YooptaContentValue` to serialize (required).
* `getEmail` only: `options` — Optional email template options.
**Returns:** String in the requested format.
```typescript theme={null}
const html = editor.getHTML(editor.children);
const md = editor.getMarkdown(editor.children);
const json = editor.getYooptaJSON(editor.children);
```
***
### History (undo/redo)
* **`editor.undo(options?)`** — Undo last batch. `options.scroll` — scroll to block; default: `true`.
* **`editor.redo(options?)`** — Redo last undone batch. Same `options`.
* **`editor.historyStack`** — `{ undos: HistoryStack[], redos: HistoryStack[] }` (internal).
* **`editor.isSavingHistory()`** — Whether history is currently being saved.
* **`editor.isMergingHistory()`** — Whether history is being merged.
* **`editor.withoutSavingHistory(fn)`** — Run `fn` without pushing to history.
* **`editor.withSavingHistory(fn)`** — Run `fn` with history saving enabled.
* **`editor.withoutMergingHistory(fn)`** — Run `fn` without merging with previous batch.
* **`editor.withMergingHistory(fn)`** — Run `fn` with history merging enabled.
***
### Batch operations and transforms
* **`editor.batchOperations(callback)`** — Run `callback`; all operations performed inside are collected and applied in a single transform. Use for multiple block/element changes that should be one undo step.
**Parameters:**
* `callback` — Function with no args; call block/element/transform methods inside it.
```typescript theme={null}
editor.batchOperations(() => {
editor.insertBlock('Paragraph', { at: 0 });
editor.insertBlock('Paragraph', { at: 1 });
});
```
* **`editor.applyTransforms(operations, options?)`** — Low-level: apply an array of Yoopta operations. Prefer block/element APIs and `batchOperations` in app code.
***
### Instance properties (read-only / internal)
* **`editor.id`** — Editor ID.
* **`editor.readOnly`** — Whether the editor is read-only.
* **`editor.formats`** — Built mark format helpers (from `marks`).
* **`editor.marks`** — Array of mark definitions passed to `createYooptaEditor`.
* **`editor.plugins`** — Resolved plugin map.
* **`editor.blockEditorsMap`** — Map of block ID to Slate editor (internal).
* **`editor.refElement`** — DOM element ref when used with the editor component.
***
### Runtime plugin management
#### registerPlugin
Registers a plugin at runtime into an already-initialized editor. Useful for dynamically adding block types — loading from a marketplace, enabling optional plugins based on user settings, or lazy-loading heavy plugins.
```typescript theme={null}
editor.registerPlugin(plugin: YooptaPlugin>): void
```
**Parameters:**
* `plugin` — A `YooptaPlugin` instance to register (required).
**Behavior:**
* If a plugin with the same type is already registered, the call is a **no-op** (silently skipped).
* The full plugin map is rebuilt, including element injection resolution (`injectElementsFromPlugins`).
* Emits `plugin:register` event with `{ type: string }`.
```typescript theme={null}
import Table from '@yoopta/table';
import Code from '@yoopta/code';
// Register a single plugin
editor.registerPlugin(Table);
// Listen for registration
editor.on('plugin:register', ({ type }) => {
console.log(`Plugin "${type}" registered`);
});
```
#### unregisterPlugin
Removes a plugin at runtime. All blocks of that plugin type are deleted from content.
```typescript theme={null}
editor.unregisterPlugin(pluginType: string): void
```
**Parameters:**
* `pluginType` — Plugin type name, PascalCase (required). E.g. `'Table'`, `'Code'`.
**Behavior:**
* If no plugin with the given type is registered, the call is a **no-op**.
* **All blocks of that type are removed** from `editor.children` and their Slate editors are cleaned up. This is destructive and cannot be undone via `editor.undo()`.
* The plugin map is rebuilt without the removed plugin.
* Emits `plugin:unregister` event with `{ type: string }`.
```typescript theme={null}
// Remove a plugin
editor.unregisterPlugin('Table');
// Toggle plugin on/off
import Code from '@yoopta/code';
function toggleCodePlugin(enabled: boolean) {
if (enabled) {
editor.registerPlugin(Code);
} else {
editor.unregisterPlugin('Code');
}
}
// Listen for removal
editor.on('plugin:unregister', ({ type }) => {
console.log(`Plugin "${type}" removed`);
});
```
# Elements API
Source: https://docs.yoopta.dev/api-reference/elements
Element-level operations within blocks for Yoopta Editor
# Elements API
The Elements API provides element-level operations inside blocks: insert, update, delete, and query. It sits below the Blocks API and works on nodes within a block’s content.
## Accessing the API
Import the `Elements` namespace from `@yoopta/editor`:
```typescript theme={null}
import { Elements } from '@yoopta/editor';
Elements.insertElement(editor, { blockId: 'accordion-1', type: 'accordion-list-item', at: 'end' });
Elements.getElement(editor, { blockId: 'accordion-1', type: 'accordion-list-item', path: [0, 1] });
Elements.deleteElement(editor, { blockId: 'accordion-1', type: 'accordion-list-item', path: [0, 1] });
```
The same functions are exported by name (`insertElement`, `getElement`, …). The `YooEditor` instance also exposes them without the first `editor` argument (e.g. `editor.insertElement({ ... })`).
## Type Definitions
```typescript theme={null}
type ElementPath = number[] | 'selection' | 'first' | 'last';
```
***
## Methods
### insertElement
Creates and inserts a new element into a block.
```typescript theme={null}
Elements.insertElement(editor: YooEditor, options: InsertElementOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type, e.g. `'accordion-list-item'`, `'link'` (required).
* `options.props` — Properties for the new element; merged with plugin defaults.
* `options.children` — Child elements; if omitted, plugin defaults are used.
* `options.at` — Where to insert: `'next'` | `'prev'` | `'start'` | `'end'` or `number[]`; default: selection or `[0]`.
* `options.focus` — Focus editor after insert; default: `false`.
* `options.select` — Select the inserted element; default: `false`.
* `options.text` — Text for inline elements (e.g. link); use `children` for block elements.
* `options.match` — Custom matcher when using `at: 'next'` or `'prev'`.
```typescript theme={null}
Elements.insertElement(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
props: { isExpanded: true },
at: 'end',
focus: true,
});
```
***
### updateElement
Updates properties (and optionally text) of an existing element.
```typescript theme={null}
Elements.updateElement(editor: YooEditor, options: UpdateElementOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type to update (required).
* `options.props` — Props to set; merged with existing.
* `options.text` — New text for inline elements (link, mention); ignored for block elements.
* `options.path` — Slate path; if omitted, uses selection (inline) or root (block).
* `options.match` — Custom matcher to find the element.
```typescript theme={null}
Elements.updateElement(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
props: { isExpanded: true },
path: [0, 1],
});
```
***
### deleteElement
Removes an element or unwraps it (inline).
```typescript theme={null}
Elements.deleteElement(editor: YooEditor, options: DeleteElementOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type to delete (required).
* `options.path` — Slate path; if omitted, uses selection (inline) or root (block).
* `options.match` — Custom matcher to find the element.
* `options.mode` — For inline only: `'unwrap'` (default) or `'remove'`. Block elements are always fully removed.
```typescript theme={null}
Elements.deleteElement(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
path: [0, 1],
});
```
***
### getElement
Returns a single element by type, path, or matcher.
```typescript theme={null}
Elements.getElement(editor: YooEditor, options: GetElementOptions): SlateElement | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type to find.
* `options.path` — Path: `number[]` or `'selection'` | `'first'` | `'last'`; default: selection or `[0]`.
* `options.match` — Custom matcher; overrides type-based search.
**Returns:** Matching element or `null`.
```typescript theme={null}
const item = Elements.getElement(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
path: [0, 1],
});
```
***
### getElements
Returns all elements of a type or matching a condition.
```typescript theme={null}
Elements.getElements(editor: YooEditor, options: GetElementsOptions): SlateElement[]
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type to find.
* `options.match` — Custom matcher; use instead of or with type.
**Returns:** Array of elements; empty if none found.
```typescript theme={null}
const items = Elements.getElements(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
});
```
***
### getElementEntry
Returns element and its Slate path (`NodeEntry`).
```typescript theme={null}
Elements.getElementEntry(
editor: YooEditor,
options: GetElementEntryOptions
): NodeEntry | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type.
* `options.path` — Path: `number[]` or `'selection'` | `'first'` | `'last'`; default: selection or `[0]`.
**Returns:** `[element, path]` or `null`.
```typescript theme={null}
const entry = Elements.getElementEntry(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
path: [0, 1],
});
if (entry) {
const [element, path] = entry;
}
```
***
### getElementPath
Returns the Slate path of an element instance.
```typescript theme={null}
Elements.getElementPath(editor: YooEditor, options: GetElementPathOptions): Path | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.element` — The element instance (required).
**Returns:** Path or `null`.
```typescript theme={null}
const item = Elements.getElement(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
path: 'first',
});
if (item) {
const path = Elements.getElementPath(editor, { blockId: 'accordion-1', element: item });
}
```
***
### getElementRect
Returns the DOM bounding box for a Slate element (`domRect` and `clientRects`).
```typescript theme={null}
Elements.getElementRect(
editor: YooEditor,
options: GetElementRectOptions
): { domRect: DOMRect; clientRects: DOMRectList } | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.element` — Slate element instance (required).
**Returns:** Rects for the DOM node, or `null` if not resolved.
```typescript theme={null}
const item = Elements.getElement(editor, { blockId: 'image-1', type: 'image', path: [0] });
if (item) {
const rect = Elements.getElementRect(editor, { blockId: 'image-1', element: item });
if (rect) {
const { width, height } = rect.domRect;
}
}
```
***
### getParentElementPath
Returns the Slate path of an element’s parent.
```typescript theme={null}
Elements.getParentElementPath(editor: YooEditor, options: GetElementPathOptions): Path | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.element` — The element instance (required).
**Returns:** Parent path or `null`.
```typescript theme={null}
const parentPath = Elements.getParentElementPath(editor, {
blockId: 'accordion-1',
element: contentElement,
});
```
***
### getElementChildren
Returns the children array of an element.
```typescript theme={null}
Elements.getElementChildren(
editor: YooEditor,
options: GetElementChildrenOptions
): SlateElement['children'] | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type (required).
* `options.path` — Path to the element; default: selection or `[0]`.
**Returns:** Children array or `null`.
```typescript theme={null}
const children = Elements.getElementChildren(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item',
path: [0, 1],
});
```
***
### getRootElement
Returns the root element definition for a given block type (plugin). Each plugin defines one or more elements — the root is either the only element in the map, or the one marked with `asRoot: true`.
```typescript theme={null}
Elements.getRootElement(editor: YooEditor, options: GetRootElementOptions): PluginElement | undefined
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockType` — Plugin type name, PascalCase (required). E.g. `'Paragraph'`, `'Accordion'`, `'Image'`.
**Returns:** The root `PluginElement` definition, or `undefined` if the plugin doesn't exist, has no elements, or no element is marked as root in a multi-element plugin.
```typescript theme={null}
// Get root element of a single-element plugin
const root = Elements.getRootElement(editor, { blockType: 'Paragraph' });
// => { render: ..., props: { nodeType: 'block' } }
// Check if a plugin's root element is void
const imageRoot = Elements.getRootElement(editor, { blockType: 'Image' });
if (imageRoot?.props?.nodeType === 'void') {
// handle void element
}
// Multi-element plugin — returns the element with asRoot: true
const accordionRoot = Elements.getRootElement(editor, { blockType: 'Accordion' });
if (accordionRoot?.children) {
console.log('Child element types:', accordionRoot.children);
}
// Filter plugins by root element node type
const blockPlugins = Object.keys(editor.plugins).filter((type) => {
const root = Elements.getRootElement(editor, { blockType: type });
const nodeType = root?.props?.nodeType;
return nodeType !== 'inline' && nodeType !== 'inlineVoid';
});
```
***
### isElementEmpty
Checks whether an element has no text content.
```typescript theme={null}
Elements.isElementEmpty(editor: YooEditor, options: IsElementEmptyOptions): boolean
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.blockId` — ID of the block (required).
* `options.type` — Element type (required).
* `options.path` — Path to the element.
**Returns:** `true` if empty, `false` otherwise.
```typescript theme={null}
const isEmpty = Elements.isElementEmpty(editor, {
blockId: 'accordion-1',
type: 'accordion-list-item-content',
path: [0, 1, 1],
});
```
# Marks API
Source: https://docs.yoopta.dev/api-reference/marks
Text formatting (marks) API for Yoopta Editor
# Marks API
The Marks API lets you apply, update, remove, and query text formatting (bold, italic, color, highlight, etc.) in the editor. Marks are applied to the current selection or to specific blocks. Use the `Marks` namespace or `createYooptaMark` to define custom marks.
## Accessing the API
Import the `Marks` namespace and `createYooptaMark` from `@yoopta/editor`:
```typescript theme={null}
import { Marks, createYooptaMark } from '@yoopta/editor';
// Apply/toggle/query marks
Marks.toggle(editor, { type: 'bold' });
Marks.add(editor, { type: 'color', value: '#ff0000' });
const isBold = Marks.isActive(editor, { type: 'bold' });
// Define custom mark (passed to createYooptaEditor)
const highlightMark = createYooptaMark({
type: 'highlight',
hotkey: 'mod+shift+h',
render: (props) => {props.children},
});
```
Marks API methods are not on the editor instance — use the `Marks` namespace. Marks must be registered via `createYooptaEditor({ marks: [...] })`.
## Type Definitions
```typescript theme={null}
import type { Range } from 'slate';
type YooptaPathIndex = number;
// Options for methods that target selection or blocks
type MarkTargetOptions = {
at?: YooptaPathIndex | YooptaPathIndex[];
blockId?: string | string[];
selection?: Range; // Slate Range; when set, at/blockId are ignored
};
// Custom mark definition (for createYooptaEditor)
type YooptaMark = {
type: string;
hotkey?: string;
render: (props: TProps) => JSX.Element;
};
```
***
## Methods
### update
Sets the value of a mark in the current selection or in specified blocks.
```typescript theme={null}
Marks.update(editor: YooEditor, options: UpdateMarkOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.type` — Mark type, e.g. `'color'`, `'fontSize'` (required).
* `options.value` — Value to set (required); e.g. `'#ff0000'`, `16`.
* `options.at` — Block index or array of indices; default: current selection path or selected blocks.
* `options.blockId` — Block ID or array of IDs.
* `options.selection` — Slate `Range`; when set, applies to this range instead of blocks.
```typescript theme={null}
Marks.update(editor, {
type: 'color',
value: '#ff0000',
});
Marks.update(editor, {
type: 'fontSize',
value: 16,
at: 0,
});
```
***
### add
Adds a mark (or sets its value) in the current selection or in specified blocks. Alias: `Marks.add` (same as `addMark`).
```typescript theme={null}
Marks.add(editor: YooEditor, options: AddMarkOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.type` — Mark type (required).
* `options.value` — Value to set (required); e.g. `true` for bold, `'#ff0000'` for color.
* `options.at` — Block index or array of indices.
* `options.blockId` — Block ID or array of IDs.
* `options.selection` — Slate `Range` to apply to.
```typescript theme={null}
Marks.add(editor, { type: 'bold', value: true });
Marks.add(editor, {
type: 'color',
value: '#ff0000',
selection: { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 5 } },
});
```
***
### remove
Removes a mark from the current selection or from specified blocks. Alias: `Marks.remove` (same as `removeMark`).
```typescript theme={null}
Marks.remove(editor: YooEditor, options: RemoveMarkOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.type` — Mark type to remove (required).
* `options.at` — Block index or array of indices.
* `options.blockId` — Block ID or array of IDs.
* `options.selection` — Slate `Range` to remove from.
```typescript theme={null}
Marks.remove(editor, { type: 'bold' });
Marks.remove(editor, { type: 'color', at: 0 });
```
***
### toggle
Toggles a mark in the current selection or in specified blocks (adds if absent, removes if present).
```typescript theme={null}
Marks.toggle(editor: YooEditor, options: ToggleMarkOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.type` — Mark type to toggle (required).
* `options.at` — Block index or array of indices.
* `options.blockId` — Block ID or array of IDs.
* `options.selection` — Slate `Range` to toggle in.
```typescript theme={null}
Marks.toggle(editor, { type: 'bold' });
Marks.toggle(editor, { type: 'italic', at: 0 });
Marks.toggle(editor, { type: 'underline', at: [0, 1, 2] });
```
***
### isActive
Returns whether a mark is active in the current selection or in a given block.
```typescript theme={null}
Marks.isActive(editor: YooEditor, options: IsMarkActiveOptions): boolean
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.type` — Mark type to check (required).
* `options.at` — Block index; default: current selection path.
* `options.blockId` — Block ID.
**Returns:** `true` if the mark is active, `false` otherwise.
```typescript theme={null}
const isBold = Marks.isActive(editor, { type: 'bold' });
const isItalic = Marks.isActive(editor, { type: 'italic', at: 0 });
```
***
### getValue
Returns the value of a mark in the current selection or in a given block.
```typescript theme={null}
Marks.getValue(editor: YooEditor, options: GetMarkValueOptions): unknown | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.type` — Mark type (required).
* `options.at` — Block index; default: current selection path.
* `options.blockId` — Block ID.
**Returns:** Mark value or `null` if not set.
```typescript theme={null}
const boldValue = Marks.getValue(editor, { type: 'bold' });
const color = Marks.getValue(editor, { type: 'color', at: 0 });
```
***
### getAll
Returns all marks in the current selection or in a given block. Alias: `Marks.getAll` (same as `getMarks`).
```typescript theme={null}
Marks.getAll(editor: YooEditor, options?: GetMarksOptions): Record | null
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Block index; default: current selection path.
* `options.blockId` — Block ID.
**Returns:** Object of mark type → value, or `null` if no Slate context.
```typescript theme={null}
const marks = Marks.getAll(editor);
const marksInBlock = Marks.getAll(editor, { at: 0 });
```
***
### clear
Removes all marks from the current selection or from specified blocks.
```typescript theme={null}
Marks.clear(editor: YooEditor, options?: ClearMarksOptions): void
```
**Parameters:**
* `editor` — Editor instance (required).
* `options.at` — Block index or array of indices; when omitted and no `selection`, uses current selection or selected blocks.
* `options.blockId` — Block ID or array of IDs.
* `options.selection` — Slate `Range` to clear.
```typescript theme={null}
Marks.clear(editor);
Marks.clear(editor, { at: 0 });
Marks.clear(editor, { at: [0, 1, 2] });
Marks.clear(editor, {
selection: { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 5 } },
});
```
***
## Defining custom marks: createYooptaMark
Use `createYooptaMark` to define a mark that you then pass to `createYooptaEditor({ marks: [...] })`. The mark has a `type`, optional `hotkey`, and a `render` function.
```typescript theme={null}
createYooptaMark(params: YooptaMarkParams): YooptaMark
```
**Parameters:**
* `params.type` — Unique mark type string (required).
* `params.hotkey` — Optional keyboard shortcut, e.g. `'mod+b'` for bold.
* `params.render` — React component that receives Slate render props and renders the marked text (required).
**Returns:** A `YooptaMark` object to add to the `marks` array when creating the editor.
```typescript theme={null}
import { createYooptaMark } from '@yoopta/editor';
const highlightMark = createYooptaMark({
type: 'highlight',
hotkey: 'mod+shift+h',
render: (props) => (
{props.children}
),
});
// In createYooptaEditor
const editor = createYooptaEditor({
plugins: [...],
marks: [highlightMark, ...defaultMarks],
});
```
# Collaboration
Source: https://docs.yoopta.dev/core/collaboration
Real-time collaboration for Yoopta Editor using Yjs CRDT
## Overview
The `@yoopta/collaboration` package enables real-time collaborative editing in Yoopta Editor. Multiple users can edit the same document simultaneously with automatic conflict resolution powered by [Yjs](https://yjs.dev/) CRDT.
The package provides:
* **`withCollaboration`** extension — connects your editor to a collaboration server
* **Collaborative undo/redo** — Cmd+Z only undoes your own changes, not other users'
* **Remote cursors** — see where other users are editing
* **Awareness** — track connected users and their presence
* **React hooks** — access collaboration state in your components
The collaboration client is **fully open-source** and works with any Yjs-compatible WebSocket server. You can self-host with your own infrastructure or use [Yoopta Cloud](#yoopta-cloud) for a managed solution with version history, permissions, and more.
## Installation
```bash theme={null}
npm install @yoopta/collaboration
```
`@yoopta/collaboration` requires `@yoopta/editor` >= 6.0.0 and React >= 18.2.0 as peer
dependencies.
## Quick Start
Wrap your editor with `withCollaboration` — similar to how `withMentions` works:
```tsx theme={null}
import { useMemo, useEffect } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import { withCollaboration, RemoteCursors } from '@yoopta/collaboration';
import { PLUGINS } from './plugins';
import { MARKS } from './marks';
export default function CollaborativeEditor() {
const editor = useMemo(
() =>
withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
url: 'wss://collab.yoopta.cloud',
roomId: 'document-123',
token: 'your-auth-token',
user: {
id: 'user-1',
name: 'Alice',
color: '#e06c75',
},
}),
[],
);
useEffect(() => {
editor.collaboration.connect();
return () => {
editor.collaboration.disconnect();
};
}, [editor.collaboration]);
return (
console.log(value)}>
{/* Your other UI components */}
);
}
```
Always call `editor.collaboration.destroy()` on cleanup to properly close the WebSocket connection
and remove awareness states.
## Configuration
### `withCollaboration(editor, config)`
Extends a `YooEditor` instance with collaboration capabilities. Returns a `CollaborationYooEditor`.
The editor instance from `createYooptaEditor()`.
Configuration object for the collaboration session.
### CollaborationConfig
WebSocket server URL. Example: `"wss://collab.yoopta.cloud"`
Unique identifier for the document/room. All users with the same `roomId` will collaborate on the
same document.
Information about the current user. Used for cursor labels and presence indicators.
```ts theme={null}
{
id: string; // Unique user identifier
name: string; // Display name (shown on cursors)
color: string; // Cursor color (hex, e.g. "#e06c75")
avatar?: string; // Optional avatar URL
}
```
Authentication token sent to the server on connection. Use this to verify users and check
subscription access on the server side.
Initial content to seed the document if no remote state exists. When the first user connects to an
empty room, this value populates the shared document.
Whether to connect to the server immediately. Set to `false` to connect manually later via
`editor.collaboration.connect()`.
Optional: provide your own Yjs `Y.Doc` instance. If not provided, one is created automatically.
Useful for advanced integrations or testing.
## Editor API
After applying `withCollaboration`, the editor gains an `editor.collaboration` namespace:
### `editor.collaboration.state`
Read-only access to the current collaboration state.
```ts theme={null}
type CollaborationState = {
status: ConnectionStatus; // 'disconnected' | 'connecting' | 'connected' | 'error'
connectedUsers: CollaborationUser[]; // All users currently in the room
document: Y.Doc | null; // The Yjs document instance
isSynced: boolean; // Whether initial sync with server is complete
};
```
### `editor.collaboration.connect()`
Manually connect to the WebSocket server. Only needed if `connect: false` was passed in config.
```ts theme={null}
editor.collaboration.connect();
```
### `editor.collaboration.disconnect()`
Disconnect from the server. The editor remains usable for local editing.
```ts theme={null}
editor.collaboration.disconnect();
```
### `editor.collaboration.destroy()`
Disconnect and clean up all resources (WebSocket, observers, awareness). Call this on component unmount.
```ts theme={null}
useEffect(() => {
return () => editor.collaboration.destroy();
}, [editor]);
```
### `editor.collaboration.getDocument()`
Returns the underlying `Y.Doc` instance for advanced use cases.
```ts theme={null}
const doc = editor.collaboration.getDocument();
```
## Components
### ``
Renders visual indicators showing which block each remote user is currently editing.
```tsx theme={null}
import { RemoteCursors } from '@yoopta/collaboration';
;
```
Must be rendered as a child of ``. Displays a colored bar and name label next to the block each remote user is editing.
## Hooks
All hooks must be used within a `` component that has collaboration enabled.
### `useCollaboration()`
Returns the full collaboration state. Re-renders on any state change.
```tsx theme={null}
import { useCollaboration } from '@yoopta/collaboration';
function StatusBar() {
const { status, connectedUsers, isSynced } = useCollaboration();
return (
);
}
```
### `useConnectionStatus()`
Returns only the connection status string. Lighter than `useCollaboration()` when you only need the status.
```tsx theme={null}
import { useConnectionStatus } from '@yoopta/collaboration';
function ConnectionIndicator() {
const status = useConnectionStatus();
return (
);
}
```
### `useRemoteCursors()`
Returns an array of remote cursor data for all connected users.
```tsx theme={null}
import { useRemoteCursors } from '@yoopta/collaboration';
function UserPresence() {
const cursors = useRemoteCursors();
return (
);
}
```
## Deferred Connection
If you need to connect after the editor is rendered (e.g., after authentication), set `connect: false`:
```tsx theme={null}
const editor = useMemo(
() =>
withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
url: 'wss://collab.yoopta.cloud',
roomId: 'doc-123',
user: { id: 'user-1', name: 'Alice', color: '#e06c75' },
connect: false, // Don't connect yet
}),
[],
);
// Connect later, e.g. after auth
function handleLogin(token: string) {
editor.collaboration.connect();
}
```
## Custom Y.Doc
For advanced use cases (e.g., testing two editors in the same page), you can provide your own `Y.Doc`:
```tsx theme={null}
import * as Y from 'yjs';
const doc = new Y.Doc();
const editor = useMemo(
() =>
withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
url: 'wss://collab.yoopta.cloud',
roomId: 'doc-123',
user: { id: 'user-1', name: 'Alice', color: '#e06c75' },
document: doc,
}),
[],
);
```
## Self-Hosted Setup
`@yoopta/collaboration` uses Yjs only as a **sync layer** — it does not store your documents. Your content remains standard Yoopta JSON (`YooptaContentValue`) that you store in your own database.
```
┌─────────────┐ load ┌──────────────────┐ sync ┌─────────────┐
│ Your DB │ ──────────────►│ Yoopta Editor │◄───────────────►│ Other │
│ (Postgres, │ initialValue │ + Y.Doc │ WebSocket │ Clients │
│ Mongo, ..) │◄──────────────┤ │ (y-protocols) │ │
└─────────────┘ save on └────────┬───────────┘ └─────────────┘
change │
│
┌──────▼──────────┐
│ Your WebSocket │
│ Server │
└─────────────────┘
```
The Y.Doc is **ephemeral** — it only lives during the collaboration session. When all users disconnect, it can be discarded. Your database is the source of truth.
### Persisting to Your Database
Load content from your database as `initialValue`, and save back on change:
```tsx theme={null}
import { useMemo, useEffect, useCallback } from 'react';
import { debounce } from 'lodash'; // or your preferred debounce
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import { withCollaboration, RemoteCursors } from '@yoopta/collaboration';
export default function CollaborativeEditor({ documentId, savedContent, currentUser }) {
const editor = useMemo(
() =>
withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
url: 'ws://localhost:4000', // Your own server
roomId: documentId,
user: currentUser,
initialValue: savedContent, // Load from your DB
}),
[],
);
// Save to your database on change
const handleChange = useCallback(
debounce((value) => {
fetch(`/api/documents/${documentId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(value),
});
}, 2000),
[documentId],
);
useEffect(() => {
editor.collaboration.connect();
return () => {
editor.collaboration.disconnect();
};
}, [editor.collaboration]);
return (
);
}
```
The `initialValue` is only used when the first user connects to an empty room. Once the Y.Doc has
content (from any client), subsequent connections load from the Y.Doc state, not from
`initialValue`.
### Running Your Own WebSocket Server
Any Yjs-compatible WebSocket server works. The simplest option is [y-websocket](https://github.com/yjs/y-websocket):
```bash theme={null}
npm install y-websocket
```
```js theme={null}
// server.js
import { WebSocketServer } from 'ws';
import { setupWSConnection } from 'y-websocket/bin/utils';
const wss = new WebSocketServer({ port: 4000 });
wss.on('connection', (ws, req) => {
// req.url is "/"
setupWSConnection(ws, req);
});
console.log('Yjs WebSocket server running on ws://localhost:4000');
```
For production, consider [Hocuspocus](https://tiptap.dev/hocuspocus) which adds authentication, persistence hooks, and webhook support out of the box.
For small teams (2-5 concurrent editors), a single y-websocket process is more than sufficient. No
special infrastructure needed.
## Yoopta Cloud
For teams that don't want to run their own collaboration infrastructure, **Yoopta Cloud** provides a managed solution:
* Managed WebSocket infrastructure with automatic scaling
* Built-in document persistence and version history
* Authentication and room-level permissions
* Comments and threads
* REST API for document access
* Webhooks for integrations (search indexing, backups, workflows)
* Analytics dashboard
```tsx theme={null}
// Using Yoopta Cloud — just change the URL and add your API key
const editor = useMemo(
() =>
withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
url: 'wss://collab.yoopta.cloud',
roomId: 'document-123',
token: 'your-api-key',
user: { id: 'user-1', name: 'Alice', color: '#e06c75' },
}),
[],
);
```
Yoopta Cloud is coming soon. The self-hosted path described above will always remain free and
open-source.
## How It Works
Under the hood, `@yoopta/collaboration` maintains a bidirectional binding between the Yoopta editor and a Yjs shared document:
```
┌──────────────────────┐ ┌──────────────────────┐
│ Yoopta Editor │ │ Y.Doc │
│ │ │ │
│ editor.children ◄──►│ binding│ Y.Array("blockOrder")│
│ block ordering │◄──────►│ Y.Map("blockMeta") │
│ block metadata │ │ Y.Map("blockContents")│
│ Slate content │ │ │
└──────────────────────┘ └──────┬───────────────┘
│
│ y-protocols (WebSocket)
│
┌──────▼───────────────┐
│ Collab Server │
└───────────────────────┘
```
* **Local changes** flow through `editor.applyTransforms` and are automatically synced to the Y.Doc, then sent to the server via WebSocket.
* **Remote changes** arrive via WebSocket, update the Y.Doc, and are applied to the editor without entering the local undo/redo history.
* **Conflict resolution** is handled by the Yjs CRDT — concurrent edits are merged automatically at the character level.
* **Undo/redo** uses Yjs `UndoManager` to selectively reverse only local changes (see below).
## Undo / Redo
When collaboration is active, `editor.undo()` and `editor.redo()` (Cmd+Z / Cmd+Shift+Z) automatically use Yjs `UndoManager` instead of the default history stack. This means:
* **Only your changes are undone** — if Alice types "Hello" and Bob types "World", Alice pressing Cmd+Z removes only "Hello". Bob's text stays intact.
* **Works at all levels** — text edits, block insertions, deletions, splits, merges, formatting, and metadata changes are all tracked.
* **No extra setup needed** — `withCollaboration` overrides undo/redo automatically. When collaboration is destroyed, the default history behavior is restored.
Rapid edits (e.g., continuous typing) are batched into a single undo step, similar to how undo works in Google Docs or Notion.
The initial document content (loaded from the server or seeded via `initialValue`) is not undoable
— only edits made after the document loads can be undone.
## TypeScript
All types are exported from the package:
```ts theme={null}
import type {
CollaborationConfig,
CollaborationUser,
CollaborationState,
ConnectionStatus,
RemoteCursorData,
CollaborationAPI,
CollaborationYooEditor,
} from '@yoopta/collaboration';
```
## WebSocketProvider
For advanced server integrations, the `WebSocketProvider` class is exported:
```ts theme={null}
import { WebSocketProvider } from '@yoopta/collaboration';
```
This is the transport layer used internally. It implements the Yjs sync and awareness protocols over WebSocket with automatic reconnection and authentication support.
# Editor
Source: https://docs.yoopta.dev/core/editor
Understanding the Yoopta Editor core component
## 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 ``. 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:
```jsx theme={null}
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 `` component. Value is managed via the editor instance and `onChange`.
## YooptaEditor Component Props
The `` component accepts:
### Required
The editor instance from `createYooptaEditor()`
### Optional
Called when content changes. Use it to sync `value` to your state (e.g. for saving or controlled usage).
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](#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).
Custom wrapper for each block (e.g. for drag-and-drop).
## Basic Example
```jsx theme={null}
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 (
);
}
```
## Placeholders
Yoopta supports placeholders at two levels that work together:
* **Editor-level** — a global fallback placeholder passed to ``
* **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:
1. Check the focused element's type (e.g. `heading-one`, `step-list-item-heading`)
2. If that element has a `placeholder` defined — use it
3. Otherwise, fall back to the editor-level `placeholder` prop
This means element-level placeholders always take priority over the global one.
### Editor-level placeholder
```jsx theme={null}
```
### Element-level placeholder
Set placeholders per element type using `.extend()`:
```jsx theme={null}
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:
```jsx theme={null}
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:
```css theme={null}
.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`.
```typescript theme={null}
type YooptaContentValue = Record;
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
```json theme={null}
{
"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:
```typescript theme={null}
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
```jsx theme={null}
```
```css theme={null}
.my-editor {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
min-height: 500px;
}
```
### Using style
```jsx theme={null}
```
## Multiple Editors
Each editor instance is independent. Create separate instances with `createYooptaEditor()` and separate state for each:
```jsx theme={null}
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 (
<>
>
);
}
```
## Next Steps
Learn about plugins and how to use them
createYooptaEditor options and YooEditor instance API
Handle editor events (on, off, emit)
# Plugins
Source: https://docs.yoopta.dev/core/plugins
Understanding and working with Yoopta Editor plugins
## Overview
Plugins are the building blocks of Yoopta Editor. Each plugin represents a specific type of content block (paragraph, heading, image, etc.) and defines how that content is rendered and behaves.
Every plugin in Yoopta Editor is built using the `YooptaPlugin` class, which provides a consistent API for defining block types, handling events, managing state, and extending functionality.
## Plugin Architecture
### YooptaPlugin Class
All plugins are created using the `YooptaPlugin` class constructor:
```typescript theme={null}
import { YooptaPlugin } from '@yoopta/editor';
const MyPlugin = new YooptaPlugin({
type: 'MyPlugin',
elements: {
// Plugin elements definition
},
options: {
// Plugin options
},
commands: {
// Plugin commands
},
events: {
// Event handlers
},
lifecycle: {
// Lifecycle hooks
},
parsers: {
// HTML, Markdown, Email parsers
},
extensions: {
// Slate extensions
},
});
```
### Plugin Structure
Each plugin configuration consists of:
Unique identifier for the plugin (e.g., 'Paragraph', 'Image', 'Code')
Defines the structure and rendering of plugin elements. Can be an object or JSX syntax
Configuration options including display settings and shortcuts
Methods for manipulating plugin content programmatically
Event handlers for keyboard, mouse, and other DOM events
Hooks that run at specific points in the plugin lifecycle
Serialization and deserialization for HTML, Markdown, and Email
Slate editor extensions for custom behavior
## Available Plugins
Yoopta Editor provides 20+ built-in plugins:
| Category | Plugins |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Text** | [Paragraph](/plugins/paragraph), [Headings](/plugins/headings) (H1, H2, H3), [Blockquote](/plugins/blockquote) |
| **Lists** | [BulletedList, NumberedList, TodoList](/plugins/lists) |
| **Media** | [Image](/plugins/image), [Video](/plugins/video), [File](/plugins/file), [Embed](/plugins/embed) |
| **Layout** | [Table](/plugins/table), [Accordion](/plugins/accordion), [Tabs](/plugins/tabs), [Steps](/plugins/steps), [Carousel](/plugins/carousel), [Callout](/plugins/callout), [Divider](/plugins/divider) |
| **Code** | [Code](/plugins/code), [CodeGroup](/plugins/code-group) |
| **Inline** | [Link](/plugins/link), [Mention](/plugins/mention), [Emoji](/plugins/emoji) |
| **Navigation** | [TableOfContents](/plugins/table-of-contents) |
## Using Plugins
Plugins are passed to **`createYooptaEditor`**, not to ``. The component only receives the `editor` instance.
### Basic Usage
```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';
import Blockquote from '@yoopta/blockquote';
import Code from '@yoopta/code';
const plugins = [Paragraph, Blockquote, Code];
export default function Editor() {
const editor = useMemo(
() => createYooptaEditor({ plugins, marks: [] }),
[],
);
return (
{}}
placeholder="Type / to open menu"
/>
);
}
```
### Headings Plugin
The Headings plugin exports multiple heading types:
```jsx theme={null}
import Headings from '@yoopta/headings';
const plugins = [
Headings.HeadingOne, // H1
Headings.HeadingTwo, // H2
Headings.HeadingThree, // H3
];
```
### Lists Plugin
The Lists plugin exports multiple list types:
```jsx theme={null}
import Lists from '@yoopta/lists';
const plugins = [Lists.BulletedList, Lists.NumberedList, Lists.TodoList];
```
## Plugin Configuration
Most plugins accept configuration options when you call `plugin.extend({ options: { ... } })`. Pass the resulting plugins array to `createYooptaEditor({ plugins, marks })`, not to ``.
### Image Plugin
```jsx theme={null}
import Image from '@yoopta/image';
const plugins = [
Image.extend({
options: {
async onUpload(file) {
// Custom upload logic
const url = await uploadToServer(file);
return { src: url };
},
maxWidth: 1000,
minWidth: 100,
},
}),
];
```
### Code Plugin
```jsx theme={null}
import Code from '@yoopta/code';
const plugins = [
Code.extend({
options: {
theme: 'github-dark',
language: 'javascript',
languages: ['javascript', 'typescript', 'python', 'rust'],
},
}),
];
```
### Video Plugin
```jsx theme={null}
import Video from '@yoopta/video';
const plugins = [
Video.extend({
options: {
async onUpload(file) {
const url = await uploadVideo(file);
return { src: url, provider: 'custom' };
},
maxSize: 100 * 1024 * 1024, // 100MB
accept: 'video/*',
},
}),
];
```
## Plugin Methods
### extend()
Extend a plugin with custom options or elements (UI):
```jsx theme={null}
const CustomParagraph = Paragraph.extend({
options: {
display: { title: 'Text', description: 'Plain text' },
shortcuts: ['p'],
},
elements: {
paragraph: {
render: (props) => (
{props.children}
),
},
},
});
```
### Defining Elements
Elements can be defined in two ways:
**1. Object Syntax:**
```typescript theme={null}
const MyPlugin = new YooptaPlugin({
type: 'MyPlugin',
elements: {
'my-element': {
render: (props) =>
{props.children}
,
props: {
/* default props */
},
nodeType: 'block', // or 'void' or 'inline'
},
},
});
```
**2. JSX Syntax (Recommended):**
```typescript theme={null}
const MyPlugin = new YooptaPlugin({
type: 'MyPlugin',
elements: (
),
});
```
### Element Properties
React component that renders the element
Default props for the element
Type of Slate node. Default is 'block'
Placeholder text shown when this element is focused and empty. Set via `.extend()`:
```jsx theme={null}
HeadingOne.extend({
elements: {
'heading-one': { placeholder: 'Heading 1' },
},
})
```
Element-level placeholders take priority over the editor-level `placeholder` prop. For nested plugins (Steps, Accordion), each element can have its own placeholder. See [Editor Placeholders](/core/editor#placeholders) for the full guide.
## Custom elements (renders)
Override default rendering by extending with custom `elements`:
```jsx theme={null}
import Paragraph from '@yoopta/paragraph';
const CustomParagraph = Paragraph.extend({
elements: {
paragraph: {
render: (props) => (
{props.children}
),
},
},
});
```
## Plugin Events
Handle plugin-specific events:
```jsx theme={null}
const CustomImage = Image.extend({
events: {
onPaste: (editor, slate, options) => {
return (event) => {
console.log('onPaste', event);
};
},
onKeyDown: (editor, slate, options) => {
return (event) => {
console.log('onKeyDown', event);
};
},
},
});
```
## Plugin shortcuts
Configure slash-menu shortcuts via `options.shortcuts` (array of strings). Plugin-specific hotkeys are defined in the plugin's `events.onKeyDown` or in options. Use `Blocks.toggleBlock(editor, 'Code', { preserveContent: true })` for programmatic block type change.
## Common Plugin Options
Most plugins support these common options:
Display configuration - `title` - Display name in UI - `description` - Plugin description - `icon`
* Custom icon component
Keyboard shortcuts to trigger the plugin
Slate-specific configuration
## Plugin Loading Order
The order of plugins in the array affects:
1. Menu display order
2. Transform priority
3. Shortcut precedence
```jsx theme={null}
const plugins = [
Paragraph, // Shows first in menus
Headings.HeadingOne,
Headings.HeadingTwo,
// ...
Divider, // Shows last in menus
];
```
## Best Practices
* Always include the **Paragraph** plugin — it's the default block type
* Define your `plugins` array **outside the component** to prevent recreation on every render
* Use **`extend()`** to customize plugins instead of modifying plugin objects directly
* For plugins with async operations (uploads), always handle errors and loading states
## Lifecycle Hooks
Lifecycle hooks allow you to run code at specific points in a block's lifecycle:
```typescript theme={null}
const MyPlugin = new YooptaPlugin({
type: 'MyPlugin',
lifecycle: {
beforeCreate: (editor) => {
// Return initial block structure
return editor.y('my-element', {
children: [editor.y.text('Initial content')],
});
},
onCreate: (editor, block) => {
// Run after block is created
},
onDestroy: (editor, block) => {
// Cleanup before block is destroyed
},
},
});
```
### Available Lifecycle Hooks
Called before a block is created. Return the initial structure
Called after a block is created
Called before a block is destroyed
## Parsers
Parsers handle serialization and deserialization of content:
```typescript theme={null}
const MyPlugin = new YooptaPlugin({
type: 'MyPlugin',
parsers: {
html: {
deserialize: {
nodeNames: ['DIV'],
parse: (el) => {
// Convert HTML element to Slate node
return {
type: 'my-element',
children: [{ text: el.textContent }],
};
},
},
serialize: (element, text, blockMeta) => {
// Convert Slate node to HTML string
return `
`;
},
},
},
});
```
## Next Steps
Learn about the basic text plugin
Working with images and uploads
Syntax-highlighted code blocks
Create and manage tables
# Themes
Source: https://docs.yoopta.dev/core/themes
Headless plugins and theme packages for styled block UI
## Overview
The editor and all plugins are **headless** by default: they define structure and behavior but do not ship UI for how block elements look. You can keep them headless (e.g. for custom design) or attach styled UI from a **theme package**.
Available themes:
* **`@yoopta/themes-shadcn`** — Shadcn UI styled components (production ready)
* **`@yoopta/themes-material`** — Material Design styled components (in progress)
Themes provide UI components for plugin elements (e.g. Callout, Code, Image). You can either apply a theme to **all** plugins at once with `applyTheme()`, or attach theme UI to **individual** plugins via `plugin.extend({ elements: ThemeUI })`.
***
## Concept: headless + optional UI
Plugins define their block type, elements, and behavior; they do not require any specific look. Theme packages export:
1. **Per-plugin UI objects** — e.g. `CalloutUI` from `@yoopta/themes-shadcn/callout`, which you pass into `Callout.extend({ elements: CalloutUI })`.
2. **`applyTheme(plugins)`** — a helper that applies the theme’s UI to every supported plugin in the array in one go.
So you can:
* Use plugins as-is (headless) and render elements yourself.
* Use theme UI for a single plugin: `Callout.extend({ elements: CalloutUI })`.
* Use theme UI for all plugins: `applyTheme([Paragraph, Callout, ...])`.
***
## Apply theme to all plugins
Use `applyTheme()` from a theme package to wrap your plugin list; the theme will attach its styled elements to every plugin it supports.
```bash theme={null}
npm install @yoopta/themes-shadcn
```
```tsx theme={null}
import { createYooptaEditor } from '@yoopta/editor';
import { applyTheme } from '@yoopta/themes-shadcn';
import Paragraph from '@yoopta/paragraph';
import Callout from '@yoopta/callout';
import Headings from '@yoopta/headings';
const plugins = applyTheme([
Paragraph,
Callout,
Headings.HeadingOne,
Headings.HeadingTwo,
Headings.HeadingThree,
]);
const editor = createYooptaEditor({ plugins, marks: [] });
```
Then pass `plugins` (the result of `applyTheme`) into `createYooptaEditor`. The theme’s CSS is applied by the package; you don’t pass plugins or marks to ``.
***
## Apply theme UI to a single plugin
If you only want themed UI for specific plugins, import the theme’s UI object for that plugin and extend the plugin with `elements`.
**Example: Callout with Shadcn UI**
```tsx theme={null}
import Callout from '@yoopta/callout';
import { CalloutUI } from '@yoopta/themes-shadcn/callout';
const CalloutWithUI = Callout.extend({
elements: CalloutUI,
});
```
Use `CalloutWithUI` in your plugins array; other plugins can stay headless or use other themes.
Subpaths are available per plugin, for example:
* `@yoopta/themes-shadcn/callout` → `CalloutUI`
* `@yoopta/themes-shadcn/paragraph` → `ParagraphUI`
* `@yoopta/themes-shadcn/embed` → `EmbedUI`
* …and other plugins supported by the theme
***
## @yoopta/themes-shadcn
Production-ready theme based on [Shadcn UI](https://ui.shadcn.com/). It provides styled elements for paragraph, headings, lists, blockquote, callout, code, image, video, embed, file, table, tabs, steps, accordion, carousel, divider, link, mention, and table-of-contents.
**Install**
```bash theme={null}
npm install @yoopta/themes-shadcn
```
**Apply to all plugins**
```tsx theme={null}
import { applyTheme } from '@yoopta/themes-shadcn';
const plugins = applyTheme([
Paragraph,
Headings.HeadingOne,
Callout,
// ...
]);
```
**Apply to one plugin**
```tsx theme={null}
import { CalloutUI } from '@yoopta/themes-shadcn/callout';
Callout.extend({ elements: CalloutUI });
```
### CSS Variables
The theme reads from standard [shadcn/ui](https://ui.shadcn.com/) CSS variables (`--background`, `--foreground`, `--card`, `--border`, etc.). If your app already uses shadcn/ui, the theme works out of the box — no extra setup needed.
If you **don't** have shadcn CSS variables defined, import the default variables provided by the package:
```tsx theme={null}
import '@yoopta/themes-shadcn/variables.css';
```
This gives you a complete set of light and dark mode color tokens.
### Dark mode
The theme supports dark mode through CSS variables. When dark mode is active, the variable values change and the editor automatically picks up the new colors (background, text, borders, etc.).
Dark mode activates when any ancestor of the editor has one of these selectors:
* `.dark` class (default for shadcn/ui and Tailwind dark mode)
* `[data-theme="dark"]` attribute
* `[data-yoopta-theme="dark"]` attribute
**Example: toggle dark mode**
```tsx theme={null}
function App() {
const [isDark, setIsDark] = useState(false);
return (
{/* ... */}
);
}
```
Or using a data attribute:
```tsx theme={null}
```
### Customizing variables
You can override any variable to match your brand. The theme uses HSL values (without the `hsl()` wrapper):
```css theme={null}
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
/* ... your dark mode overrides */
}
```
You only need to define the variables you want to change — the rest will use defaults from `variables.css` (if imported) or your existing shadcn/ui setup.
**Full list of variables used by the theme:**
| Variable | Usage |
| -------------------------------------------- | --------------------------------------- |
| `--background` | Editor background |
| `--foreground` | Editor text color |
| `--card` / `--card-foreground` | Card-style blocks (callout, code, etc.) |
| `--popover` / `--popover-foreground` | Dropdowns, menus, tooltips |
| `--primary` / `--primary-foreground` | Primary actions, buttons |
| `--secondary` / `--secondary-foreground` | Secondary actions |
| `--muted` / `--muted-foreground` | Muted text, disabled states |
| `--accent` / `--accent-foreground` | Hover highlights, accents |
| `--destructive` / `--destructive-foreground` | Delete actions, errors |
| `--border` | Borders |
| `--input` | Input field borders |
| `--ring` | Focus rings |
| `--radius` | Border radius base value |
***
## @yoopta/themes-material
Material Design styled components for Yoopta plugins. **In progress** — not all plugins may be covered yet.
**Install**
```bash theme={null}
npm install @yoopta/themes-material
```
**Usage** (same idea as Shadcn: `applyTheme(plugins)` or `plugin.extend({ elements: MaterialUI })` per plugin when the package exposes it).
***
## Summary
| Approach | Use case |
| -------------------------------------- | ----------------------------------------------- |
| Headless | Custom design; you implement all element UI. |
| `plugin.extend({ elements: ThemeUI })` | Use theme UI only for specific plugins. |
| `applyTheme(plugins)` | Use theme UI for all supported plugins at once. |
The editor and plugins stay headless; themes only provide optional UI for their elements.
# Introduction
Source: https://docs.yoopta.dev/introduction
Learn about Yoopta Editor — a headless, plugin-based rich-text editor for React
## Why Yoopta?
I've integrated rich-text editors into products more times than I'd like to admit. Every time it was the same story: wrestling with complex APIs, fighting against opinionated UI that doesn't fit your product, hacking around limitations.
Yoopta was built to end that cycle. The idea is simple: give developers a **headless core** when they need full control, but also ship **20+ ready-made plugins**, **pre-built UI components** (toolbars, slash menus, block actions), and **theme presets** (shadcn, Material) — so you can launch a polished editing experience without building everything from scratch.
Whether you're building a simple blog editor, a Notion-like workspace, a CMS for landing pages, or even a full website builder — Yoopta gives you the primitives to get there.
If Yoopta saves you time, consider [starring the repo](https://github.com/Darginec05/Yoopta-Editor) or [sponsoring the project](https://github.com/sponsors/Darginec05). It keeps the project alive.
## How it works
Yoopta Editor is **headless** — the core package (`@yoopta/editor`) handles all content logic (blocks, elements, operations, history) but renders no UI. You get full control over appearance.
Don't want to build everything yourself? Yoopta also ships:
* **Themes** (`@yoopta/themes-shadcn`) — pre-styled components you can apply in one line with `applyTheme()`
* **UI components** (`@yoopta/ui`) — toolbar, slash command menu, block actions, and more
* **20+ plugins** — paragraph, headings, lists, tables, images, code, embeds, and more
Each block in the editor runs its own [Slate.js](https://docs.slatejs.org/) instance, which means blocks are isolated, performant, and independently extensible.
## Examples & Demos
See what you can build with Yoopta:
Complete editor with toolbar, slash menu, block actions, drag & drop, and mentions
Visual page builder with drag-and-drop blocks, sidebar settings, and live preview
Microsoft Word-inspired editor with fixed toolbar, formatting, and export
Email composition with templates, split-view editing, and email-safe export
GitHub-flavored Markdown editor with live preview and download as README.md
Slack-style messaging with channel list, rich text composer, and mentions
WhatsApp/Instagram-like chat with message bubbles, attachments, and reactions
Real-time multi-user editing with Yjs, remote cursors, and presence awareness
All examples with source code are available in the [examples directory](https://github.com/Darginec05/Yoopta-Editor/tree/main/web/next-app-example/app/examples).
## Quick Example
```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';
import { Bold, Italic } from '@yoopta/marks';
import { applyTheme } from '@yoopta/themes-shadcn';
const plugins = applyTheme([Paragraph]);
const marks = [Bold, Italic];
export default function Editor() {
const editor = useMemo(() => createYooptaEditor({ plugins, marks }), []);
return (
console.log(value)}
placeholder="Type something..."
/>
);
}
```
## Packages
Install only what you need:
| Category | Packages | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| **Core** | `@yoopta/editor` | Headless editor engine (requires `slate`, `slate-react`, `slate-dom`) |
| **UI** | `@yoopta/ui` | Toolbar, ActionMenuList, SlashCommandMenu, BlockOptions, FloatingBlockActions |
| **Marks** | `@yoopta/marks` | Bold, Italic, Underline, Strike, Code, Highlight |
| **Text** | `@yoopta/paragraph`, `@yoopta/headings`, `@yoopta/blockquote` | Basic text blocks |
| **Lists** | `@yoopta/lists` | Bulleted, numbered, and todo lists |
| **Media** | `@yoopta/image`, `@yoopta/video`, `@yoopta/file`, `@yoopta/embed` | Media and embed blocks |
| **Layout** | `@yoopta/table`, `@yoopta/accordion`, `@yoopta/tabs`, `@yoopta/steps`, `@yoopta/carousel`, `@yoopta/callout`, `@yoopta/divider` | Structured layout blocks |
| **Inline** | `@yoopta/link`, `@yoopta/mention`, `@yoopta/emoji`, `@yoopta/math` | Inline elements |
| **Code** | `@yoopta/code` | Code block with syntax highlighting |
| **Math** | `@yoopta/math` | Code block with syntax highlighting |
| **Themes** | `@yoopta/themes-shadcn`, `@yoopta/themes-material` | Pre-styled plugin components |
| **Exports** | `@yoopta/exports` | HTML, Markdown, plain text, email serializers |
| **Collaboration** | `@yoopta/collaboration` | Real-time collaboration via Yjs |
## Key Features
Full control over UI. Use your own components, apply a theme, or mix both.
20+ built-in plugins. Create custom ones or extend existing plugins with `.extend()`.
Inject plugins inside other plugins — lists inside callouts, code inside tabs, and more.
Reorder blocks, nest content, and drag elements with built-in DnD support.
Serialize to HTML, Markdown, plain text, or email-safe HTML out of the box.
Build CMS dashboards, landing page builders, or full website builders — Yoopta provides the
content primitives, you define the experience.
Full type safety across the editor, plugins, and all APIs.
## Next Steps
Install and build your first editor in 5 minutes
Deep dive into editor architecture
Complete API documentation
## Community & Support
* **[Discord](https://discord.gg/Dt8rhSTjsn)** — Chat with the team and community
* **[GitHub Discussions](https://github.com/Darginec05/Yoopta-Editor/discussions)** — Ask questions and share ideas
* **[GitHub Issues](https://github.com/Darginec05/Yoopta-Editor/issues)** — Report bugs or request features
* **[Twitter](https://twitter.com/LebovskiYoo)** — Follow for updates
If Yoopta Editor is useful to you, please give it a star on
[GitHub](https://github.com/Darginec05/Yoopta-Editor)!
# Marks Overview
Source: https://docs.yoopta.dev/marks/overview
Text formatting (marks) in Yoopta Editor
## Introduction
**Marks** in Yoopta Editor are text formatting options: bold, italic, underline, strike, code, highlight, and custom marks (e.g. color, font size). They are applied to the current selection or to specific blocks. Marks are registered when you create the editor and controlled via the `Marks` namespace.
Marks are not on the editor instance—use the `Marks` namespace from `@yoopta/editor`. Register marks in `createYooptaEditor({ marks: [...] })`.
## Built-in marks
The `@yoopta/marks` package provides ready-to-use marks:
* **Bold** — `Cmd/Ctrl + B`
* **Italic** — `Cmd/Ctrl + I`
* **Underline** — `Cmd/Ctrl + U`
* **Strike** — `Cmd/Ctrl + Shift + S`
* **CodeMark** — `Cmd/Ctrl + E`
* **Highlight** — Text highlighting with colors
Install and pass them to `createYooptaEditor`:
```bash theme={null}
yarn add @yoopta/marks
```
```tsx theme={null}
import { createYooptaEditor } from '@yoopta/editor';
import { Bold, Italic, Underline, Strike, CodeMark, Highlight } from '@yoopta/marks';
const marks = [Bold, Italic, Underline, Strike, CodeMark, Highlight];
const editor = createYooptaEditor({
plugins: PLUGINS,
marks,
});
```
## Using the Marks API
Apply, toggle, or query marks with the `Marks` namespace:
```tsx theme={null}
import { Marks } from '@yoopta/editor';
// Toggle bold on current selection
Marks.toggle(editor, { type: 'bold' });
// Add color to selection
Marks.add(editor, { type: 'color', value: '#ff0000' });
// Check if bold is active
const isBold = Marks.isActive(editor, { type: 'bold' });
// Get current mark value
const color = Marks.getValue(editor, { type: 'color' });
// Remove a mark
Marks.remove(editor, { type: 'bold' });
// Clear all marks
Marks.clear(editor);
```
You can target specific blocks with `at` or `blockId`, or a Slate `selection` range. See [Marks API](/api-reference/marks) for all methods and options.
## Custom marks
Define your own marks with `createYooptaMark`: give them a `type`, optional `hotkey`, and a `render` function. Pass the result into `createYooptaEditor({ marks: [...] })`.
```tsx theme={null}
import { createYooptaMark } from '@yoopta/editor';
const HighlightMark = createYooptaMark({
type: 'highlight',
hotkey: 'mod+shift+h',
render: (props) => (
{props.children}
),
});
const editor = createYooptaEditor({
plugins: PLUGINS,
marks: [Bold, Italic, HighlightMark],
});
```
## Next steps
Full API: update, add, remove, toggle, isActive, getValue, getAll, clear
createYooptaEditor options and YooEditor instance
# Accordion
Source: https://docs.yoopta.dev/plugins/accordion
Collapsible accordion sections
## Overview
The Accordion plugin creates collapsible content sections. Each item has a heading (click to expand/collapse) and content area. It uses native `` / `` in the default render and supports HTML serialization from those elements.
## Installation
```bash theme={null}
npm install @yoopta/accordion
```
## Basic Usage
Pass the plugin to `createYooptaEditor`; do not pass `plugins` to ``.
```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Accordion from '@yoopta/accordion';
const plugins = [Accordion];
export default function Editor() {
const editor = useMemo(() => createYooptaEditor({ plugins, marks: [] }), []);
return {}} />;
}
```
## Features
* **Expand/Collapse**: Each item can be expanded or collapsed via `isExpanded` prop
* **Keyboard**: Enter toggles expansion when in heading, or inserts a new item; Backspace removes empty items or the block
* **HTML**: Deserializes from `` / `` / `
`; serializes to the same
* **Email**: Serializes to table-based layout for email clients
* **Shortcuts**: Type `accordion` to insert
## Structure
The Accordion block consists of nested elements:
```
accordion-list
└── accordion-list-item (multiple)
├── accordion-list-item-heading
└── accordion-list-item-content
```
## Configuration
```jsx theme={null}
import Accordion from '@yoopta/accordion';
const plugins = [Accordion];
```
## Options
Display title in UI menus
Description shown in menus
Keyboard shortcuts to trigger the plugin in slash menu
## Element props
Whether the accordion item is expanded (open) or collapsed
## Commands
Accordion commands are available via the plugin's `commands` and through the editor. Use the **`Blocks`** namespace from `@yoopta/editor` for generic block operations (insert, delete, update meta) and **Accordion commands** for accordion structure.
### insertAccordion
Insert a new Accordion block.
```typescript theme={null}
import Accordion, { AccordionCommands } from '@yoopta/accordion';
AccordionCommands.insertAccordion(editor, {
items: 2,
props: { isExpanded: false },
at: 0,
focus: true,
});
```
Number of accordion items to create initially
Initial expanded state for each item
Index or path where to insert the block
Whether to focus the new block after insert
### buildAccordionElements
Build the accordion element structure (used internally by `insertAccordion`).
```typescript theme={null}
import { AccordionCommands } from '@yoopta/accordion';
const accordionList = AccordionCommands.buildAccordionElements(editor, {
items: 3,
props: { isExpanded: true },
});
```
### deleteAccordion
Remove an Accordion block by id.
```typescript theme={null}
import { AccordionCommands } from '@yoopta/accordion';
AccordionCommands.deleteAccordion(editor, blockId);
```
## Block operations
You can also use the generic Blocks API:
```typescript theme={null}
import { Blocks } from '@yoopta/editor';
// Insert (use AccordionCommands.insertAccordion for correct initial structure)
Blocks.insertBlock(editor, 'Accordion', { blockData: { ... } });
// Delete
Blocks.deleteBlock(editor, { blockId });
// Update block meta (e.g. align, depth)
Blocks.updateBlock(editor, blockId, { meta: { align: 'center' } });
```
## Element operations
To toggle an item's expanded state or add/remove items, use the Elements API:
```typescript theme={null}
import { Elements } from '@yoopta/editor';
// Toggle expanded state of an accordion item
Elements.updateElement(editor, {
blockId,
type: 'accordion-list-item',
props: { isExpanded: !currentExpanded },
path: listItemPath,
});
// Insert new accordion item
Elements.insertElement(editor, {
blockId,
type: 'accordion-list-item',
props: { isExpanded: true },
at: 'next',
focus: true,
});
// Delete accordion item
Elements.deleteElement(editor, {
blockId,
type: 'accordion-list-item',
path: listItemPath,
});
```
## Keyboard behavior
* **Enter** in heading: toggles the item's expanded state
* **Enter** in content: inserts a new accordion item after the current one
* **Backspace** in empty heading: removes the item (or deletes the block if it's the only item)
* **Backspace** in empty content (when focus is in content): prevented so structure is preserved
## Theme (Shadcn)
The `@yoopta/themes-shadcn` package provides styled accordion elements. Apply the theme so the Accordion block uses Shadcn UI:
```jsx theme={null}
import { applyTheme } from '@yoopta/themes-shadcn';
import Accordion from '@yoopta/accordion';
const plugins = applyTheme([Accordion]);
```
The Shadcn accordion theme maps:
* `accordion-list` → `AccordionList`
* `accordion-list-item` → `AccordionListItem` (uses `element.props.isExpanded` for `data-state`)
* `accordion-list-item-heading` → `AccordionItemHeading`
* `accordion-list-item-content` → `AccordionItemContent`
## Parsers
### HTML
* **Deserialize**: `` with `` and `
` children
* **Serialize**: Accordion block → `
` containing `` / `` / `
`; block meta `align` and `depth` are stored in `data-meta-align` and `data-meta-depth` on each ``
### Email
* **Serialize**: Table-based layout for compatibility with email clients; headings and content in table rows with inline styles.
## Related
* [Tabs](/plugins/tabs) — Tabbed content
* [Steps](/plugins/steps) — Step-by-step instructions
* [Blockquote](/plugins/blockquote) — Quote blocks
# Blockquote
Source: https://docs.yoopta.dev/plugins/blockquote
Quote blocks
## Overview
The Blockquote plugin provides styled quote blocks for highlighting citations, testimonials, or important excerpts.
# Callout
Source: https://docs.yoopta.dev/plugins/callout
Highlighted callout blocks
## Overview
The Callout plugin creates highlighted notice blocks for tips, warnings, notes, or other call-to-attention content.
# Carousel
Source: https://docs.yoopta.dev/plugins/carousel
Display images or content in a carousel/slider format
## Overview
The Carousel plugin allows you to create interactive carousels to display images or content in a slider format. It's perfect for showcasing multiple items in a compact, scrollable interface.
## Installation
```bash theme={null}
npm install @yoopta/carousel
```
## Basic Usage
Pass the plugin to `createYooptaEditor`; do not pass `plugins` to ``.
```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Carousel from '@yoopta/carousel';
const plugins = [Carousel];
export default function Editor() {
const editor = useMemo(() => createYooptaEditor({ plugins, marks: [] }), []);
return {}} />;
}
```
## Features
* **Multiple Items**: Display multiple carousel items
* **Keyboard Shortcuts**: Type `carousel` to insert
* **Responsive**: Adapts to different screen sizes
* **Customizable**: Extend with custom rendering
## Structure
The Carousel plugin consists of nested elements:
```
carousel-container
└── carousel-list-item (multiple)
├── content
└── ...
```
## Commands
### buildCarouselElements
Creates the initial carousel structure.
```typescript theme={null}
CarouselCommands.buildCarouselElements(editor, { items: 5 });
```
Number of carousel items to create (default: 5)
## Extending the Plugin
### Custom Rendering
```jsx theme={null}
import { CarouselPlugin } from '@yoopta/carousel';
const CustomCarousel = CarouselPlugin.extend({
elements: {
'carousel-container': {
render: (props) => (
),
},
},
});
```
### Integration with Embla Carousel
For advanced carousel functionality, you can integrate with [Embla Carousel](https://www.embla-carousel.com/):
```jsx theme={null}
import { CarouselPlugin } from '@yoopta/carousel';
import useEmblaCarousel from 'embla-carousel-react';
const CustomCarousel = CarouselPlugin.extend({
elements: {
'carousel-container': {
render: (props) => {
const [emblaRef] = useEmblaCarousel({ loop: true });
return (
{props.children}
);
},
},
},
});
```
## Options
Display title in UI
Description shown in menus
Keyboard shortcuts to trigger the plugin
## Use Cases
Display multiple images in a scrollable carousel
Show product features or variations
Rotate through customer testimonials
Showcase portfolio items or projects
## Best Practices
Ensure images are optimized for web to maintain carousel performance
Keep carousel items reasonable (5-10) for better UX
Provide clear navigation controls for users
## Related Plugins
* [Image Plugin](/plugins/image) - For individual images
* [Tabs Plugin](/plugins/tabs) - For tabbed content organization
# Code
Source: https://docs.yoopta.dev/plugins/code
Syntax-highlighted code blocks with theme support
## Overview
The Code plugin provides syntax-highlighted code blocks powered by [Shiki](https://shiki.style/). It supports multiple programming languages and themes, making it perfect for technical documentation and tutorials.
## Installation
```bash theme={null}
npm install @yoopta/code
```
## Basic Usage
Pass the plugin to `createYooptaEditor`; do not pass `plugins` to ``.
```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Code from '@yoopta/code';
const plugins = [Code];
export default function Editor() {
const editor = useMemo(() => createYooptaEditor({ plugins, marks: [] }), []);
return {}} />;
}
```
## Features
* **Syntax Highlighting**: Powered by Shiki with accurate highlighting
* **200+ Languages**: Support for all major programming languages
* **Multiple Themes**: Choose from various color themes
* **Code Copying**: Easy code copying functionality
* **Line Numbers**: Optional line number display
* **Keyboard Shortcuts**: Type ` ``` `, `code`, or `js` to insert
## Configuration
```jsx theme={null}
import { Code } from '@yoopta/code';
const plugins = [
Code.extend({
options: {
theme: 'github-dark',
language: 'typescript',
},
}),
];
```
## Options
Shiki theme for syntax highlighting. Popular options: - `github-dark` - `github-light` - `dracula`
* `monokai` - `nord` - `tokyo-night`
Default programming language. Supported languages include: - JavaScript, TypeScript - Python,
Java, C++, Go, Rust - HTML, CSS, SCSS - JSON, YAML, XML - And 190+ more
Display title in UI
Description shown in menus
Keyboard shortcuts to trigger the plugin
## Element Props
Each code block has the following properties:
Programming language for syntax highlighting
Color theme for the code block
## Commands
Use the **`Blocks`** namespace for block type changes and **`CodeCommands`** (or **`Elements.updateElement`**) for code element props:
```typescript theme={null}
import { Blocks } from '@yoopta/editor';
import { CodeCommands } from '@yoopta/code';
// Transform current block to a Code block
Blocks.toggleBlock(editor, 'Code', { preserveContent: true });
// Update language / theme (recommended — keeps Slate structure valid)
CodeCommands.updateCodeLanguage(editor, blockId, 'python');
CodeCommands.updateCodeTheme(editor, blockId, 'dracula');
```
## Custom Rendering
```jsx theme={null}
import { Code } from '@yoopta/code';
const CustomCode = Code.extend({
elements: {
code: {
render: (props) => {
const { language, theme } = props.element.props;
return (
{language}
{props.children}
);
},
},
},
});
```
## Keyboard Behavior
The Code plugin has special keyboard handling:
* **Enter**: Inserts a newline within the code block (not a new block)
* **Shift+Enter**: Also inserts a newline
* **Tab**: Inserts tab character (doesn't switch focus)
## Parsers
### HTML Serialization
```html theme={null}
);
},
},
},
});
```
## Use Cases
Technical documentation with code examplesStep-by-step coding tutorialsAPI endpoints and code samplesTechnical blog posts with code snippets
## Best Practices
Select a theme that matches your site's design and provides good contrast
Always specify the language for accurate syntax highlighting
Break long code examples into smaller, focused snippets
Include comments in code to explain complex logic
## Related Plugins
* [CodeGroup Plugin](/plugins/code-group) - For tabbed code blocks
* [Paragraph Plugin](/plugins/paragraph) - For text content
# CodeGroup
Source: https://docs.yoopta.dev/plugins/code-group
Display multiple code files in tabbed interface
## Overview
The CodeGroup plugin allows you to create tabbed code blocks, perfect for showing the same code in different languages or displaying multiple related files. Each tab can have its own language and syntax highlighting.
## Installation
```bash theme={null}
npm install @yoopta/code
```
## Basic Usage
Pass the plugin to `createYooptaEditor`; do not pass `plugins` to ``.
```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import { CodeGroup } from '@yoopta/code';
const plugins = [CodeGroup];
export default function Editor() {
const editor = useMemo(() => createYooptaEditor({ plugins, marks: [] }), []);
return {}} />;
}
```
## Features
* **Multiple Tabs**: Display code in multiple tabs
* **Syntax Highlighting**: Powered by Shiki
* **Per-Tab Languages**: Each tab can have different language
* **Active Tab State**: Track which tab is active
* **Theme Support**: Consistent theming across tabs
## Structure
The CodeGroup plugin consists of nested elements:
```
code-group-container (props: activeTabId, theme)
├── code-group-list
│ └── code-group-item-heading (multiple tabs)
└── code-group-content (props: referenceId, language)
```
## Configuration
```jsx theme={null}
import { CodeGroup } from '@yoopta/code';
const plugins = [
CodeGroup.extend({
options: {
theme: 'dracula',
},
}),
];
```
## Options
Shiki theme for all code blocks in the group
Display title in UI
Description shown in menus
## Element Props
### code-group-container
ID of the currently active tab
Theme for all code blocks
### code-group-content
ID linking content to its tab heading
Programming language for this code block
## Commands
### addTabItem
Add a new tab to the code group.
```typescript theme={null}
import { CodeGroupCommands } from '@yoopta/code';
CodeGroupCommands.addTabItem(editor, blockId, { at: selection });
```
## Keyboard Behavior
* **Enter on Tab Heading**: Creates a new tab
* **Enter in Code Content**: Inserts newline (not a new block)
* **Backspace**: Prevents deletion at content start
* **Cmd/Ctrl+A**: Selects entire code content
## Initial Structure
When created, the plugin initializes with:
```typescript theme={null}
{
'code-group-container': {
activeTabId: 'generated-id',
theme: 'github-dark',
},
tabs: [
{
heading: 'hello-world.ts',
content: 'console.log("Hello World");',
language: 'typescript',
},
],
}
```
## Custom Rendering
```jsx theme={null}
import { CodeGroup } from '@yoopta/code';
const CustomCodeGroup = CodeGroup.extend({
elements: {
'code-group-container': {
render: (props) => {
const { activeTabId, theme } = props.element.props;
return (