diff --git a/apps/docs/editor/advanced/custom-extensions.mdx b/apps/docs/editor/advanced/custom-extensions.mdx
new file mode 100644
index 0000000000..2aa9ce0ff0
--- /dev/null
+++ b/apps/docs/editor/advanced/custom-extensions.mdx
@@ -0,0 +1,290 @@
+---
+title: "Custom Extensions"
+sidebarTitle: "Custom Extensions"
+description: "Create custom email-compatible editor nodes and marks."
+icon: "wrench"
+---
+
+## EmailNode vs TipTap Node
+
+The editor uses `EmailNode` instead of TipTap's standard `Node`. The key difference is a
+required `renderToReactEmail()` method that tells the serializer how to convert the node to
+a React Email component for HTML export.
+
+```
+TipTap Node
+├── name, group, content
+├── parseHTML()
+├── renderHTML() ← How it looks in the editor
+└── ...
+
+EmailNode (extends Node)
+├── name, group, content
+├── parseHTML()
+├── renderHTML() ← How it looks in the editor
+├── renderToReactEmail() ← How it looks in the exported email HTML
+└── ...
+```
+
+## Creating a custom node
+
+Here's a complete example of a custom "Callout" node that renders as a highlighted block:
+
+```tsx
+import { EmailNode } from '@react-email/editor/core';
+import { mergeAttributes } from '@tiptap/core';
+
+const Callout = EmailNode.create({
+ name: 'callout',
+ group: 'block',
+ content: 'inline*',
+
+ parseHTML() {
+ return [{ tag: 'div[data-callout]' }];
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return [
+ 'div',
+ mergeAttributes(HTMLAttributes, {
+ 'data-callout': '',
+ style:
+ 'padding: 12px 16px; background: #f4f4f5; border-left: 3px solid #1c1c1c; border-radius: 4px; margin: 8px 0;',
+ }),
+ 0,
+ ];
+ },
+
+ renderToReactEmail({ children, style }) {
+ return (
+
+ {children}
+
+ );
+ },
+});
+```
+
+Key methods:
+
+- **`parseHTML()`** — Defines which HTML elements get parsed into this node (for clipboard paste, HTML content)
+- **`renderHTML()`** — Controls how the node appears in the editor (in the browser DOM)
+- **`renderToReactEmail()`** — Controls how the node is serialized when exporting to email HTML via `composeReactEmail`
+
+## Registering the extension
+
+Add your custom extension to the extensions array alongside `StarterKit`:
+
+```tsx
+const extensions = [StarterKit, Callout];
+```
+
+## Inserting custom nodes
+
+Use the editor's `insertContent` command to programmatically insert your custom node:
+
+```tsx
+import { useCurrentEditor } from '@tiptap/react';
+
+function Toolbar() {
+ const { editor } = useCurrentEditor();
+ if (!editor) return null;
+
+ return (
+
+ );
+}
+```
+
+## Complete example
+
+Here's the full editor setup with the custom Callout extension, a toolbar, and a bubble menu:
+
+```tsx
+import { EmailNode } from '@react-email/editor/core';
+import { StarterKit } from '@react-email/editor/extensions';
+import { BubbleMenu } from '@react-email/editor/ui';
+import { mergeAttributes } from '@tiptap/core';
+import { EditorProvider, useCurrentEditor } from '@tiptap/react';
+import { Info } from 'lucide-react';
+
+const Callout = EmailNode.create({
+ name: 'callout',
+ group: 'block',
+ content: 'inline*',
+
+ parseHTML() {
+ return [{ tag: 'div[data-callout]' }];
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return [
+ 'div',
+ mergeAttributes(HTMLAttributes, {
+ 'data-callout': '',
+ style:
+ 'padding: 12px 16px; background: #f4f4f5; border-left: 3px solid #1c1c1c; border-radius: 4px; margin: 8px 0;',
+ }),
+ 0,
+ ];
+ },
+
+ renderToReactEmail({ children, style }) {
+ return (
+
+ {children}
+
+ );
+ },
+});
+
+const extensions = [StarterKit, Callout];
+
+const content = {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This editor includes a custom Callout node. Use the toolbar to insert one.',
+ },
+ ],
+ },
+ {
+ type: 'callout',
+ content: [
+ { type: 'text', text: 'This is a callout block — a custom extension!' },
+ ],
+ },
+ ],
+};
+
+function Toolbar() {
+ const { editor } = useCurrentEditor();
+ if (!editor) return null;
+
+ return (
+
+ );
+}
+
+export function MyEditor() {
+ return (
+ }
+ >
+
+
+ );
+}
+```
+
+## EmailNode.from
+
+Wrap an existing TipTap node with email serialization support:
+
+```tsx
+import { EmailNode } from '@react-email/editor/core';
+import { Node } from '@tiptap/core';
+
+const MyTipTapNode = Node.create({ /* ... */ });
+
+const MyEmailNode = EmailNode.from(MyTipTapNode, ({ children, style }) => {
+ return {children}
;
+});
+```
+
+This is useful when you want to reuse a community TipTap extension and add email export support.
+
+## EmailMark
+
+For inline marks (like bold, italic, or custom annotations), use `EmailMark`:
+
+```tsx
+import { EmailMark } from '@react-email/editor/core';
+
+const Highlight = EmailMark.create({
+ name: 'highlight',
+
+ parseHTML() {
+ return [{ tag: 'mark' }];
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['mark', HTMLAttributes, 0];
+ },
+
+ renderToReactEmail({ children, style }) {
+ return (
+ {children}
+ );
+ },
+});
+```
+
+## Configure and extend
+
+Both `EmailNode` and `EmailMark` support TipTap's standard customization methods:
+
+```tsx
+// Configure options
+const CustomHeading = Heading.configure({ levels: [1, 2] });
+
+// Extend with additional behavior
+const CustomParagraph = Paragraph.extend({
+ addKeyboardShortcuts() {
+ return {
+ 'Mod-Shift-p': () => this.editor.commands.setParagraph(),
+ };
+ },
+});
+```
diff --git a/apps/docs/editor/advanced/event-bus.mdx b/apps/docs/editor/advanced/event-bus.mdx
new file mode 100644
index 0000000000..be4ab21ab9
--- /dev/null
+++ b/apps/docs/editor/advanced/event-bus.mdx
@@ -0,0 +1,102 @@
+---
+title: "Event Bus"
+sidebarTitle: "Event Bus"
+description: "Communicate between editor components using typed events."
+icon: "tower-broadcast"
+---
+
+## Overview
+
+The editor provides a typed event bus for communication between components. It's a singleton
+instance built on the browser's native `CustomEvent` API with events prefixed as
+`@react-email/editor:`.
+
+```tsx
+import { editorEventBus } from '@react-email/editor/core';
+```
+
+## Dispatching events
+
+Fire an event with a payload:
+
+```tsx
+editorEventBus.dispatch('bubble-menu:add-link', undefined);
+```
+
+## Listening to events
+
+Subscribe to events and clean up when done:
+
+```tsx
+import { useEffect } from 'react';
+import { editorEventBus } from '@react-email/editor/core';
+
+function MyComponent() {
+ useEffect(() => {
+ const subscription = editorEventBus.on('bubble-menu:add-link', () => {
+ console.log('Link addition triggered');
+ });
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, []);
+
+ return null;
+}
+```
+
+The `on` method returns an object with an `unsubscribe` function. Always unsubscribe in a
+cleanup function to avoid memory leaks.
+
+## Built-in events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `bubble-menu:add-link` | `undefined` | Triggered when the "add link" action is initiated from the bubble menu |
+
+## Adding custom events
+
+Use TypeScript module augmentation to register custom events with full type safety:
+
+```tsx
+declare module '@react-email/editor/core' {
+ interface EditorEventMap {
+ 'my-feature:custom-event': { data: string };
+ 'my-feature:another-event': { count: number };
+ }
+}
+```
+
+Then dispatch and listen with full type checking:
+
+```tsx
+// TypeScript knows the payload type
+editorEventBus.dispatch('my-feature:custom-event', { data: 'hello' });
+
+editorEventBus.on('my-feature:custom-event', (payload) => {
+ // payload is typed as { data: string }
+ console.log(payload.data);
+});
+```
+
+## Event targets
+
+By default, events are dispatched on `window`. You can scope events to a specific DOM element
+using the `target` option:
+
+```tsx
+// Dispatch on a specific element
+const container = document.getElementById('my-editor');
+editorEventBus.dispatch('bubble-menu:add-link', undefined, {
+ target: container,
+});
+
+// Listen on a specific element
+editorEventBus.on('bubble-menu:add-link', handler, {
+ target: container,
+});
+```
+
+This is useful when you have multiple editor instances on the same page and want events
+to stay scoped to their respective editors.
diff --git a/apps/docs/editor/advanced/extensions.mdx b/apps/docs/editor/advanced/extensions.mdx
new file mode 100644
index 0000000000..894b376405
--- /dev/null
+++ b/apps/docs/editor/advanced/extensions.mdx
@@ -0,0 +1,159 @@
+---
+title: "Extensions"
+sidebarTitle: "Extensions"
+description: "Configure and customize the editor's built-in extensions."
+icon: "puzzle-piece"
+---
+
+## StarterKit
+
+`StarterKit` bundles all 35+ editor extensions into a single import. It wraps TipTap's
+StarterKit and replaces most nodes with email-aware versions that know how to serialize
+to React Email components.
+
+```tsx
+import { StarterKit } from '@react-email/editor/extensions';
+
+const extensions = [StarterKit];
+```
+
+
+ Use `StarterKit` unless you need fine-grained control over which extensions are loaded.
+ It's the recommended way to set up the editor.
+
+
+## Configuring extensions
+
+Pass options to configure individual extensions, or set them to `false` to disable:
+
+```tsx
+const extensions = [
+ StarterKit.configure({
+ // Customize heading levels
+ Heading: { levels: [1, 2] },
+
+ // Disable strikethrough
+ Strike: false,
+
+ // Disable code blocks
+ CodeBlockPrism: false,
+ }),
+];
+```
+
+## Using individual extensions
+
+For maximum control, import and compose extensions manually instead of using StarterKit:
+
+```tsx
+import {
+ Body,
+ Bold,
+ Heading,
+ Italic,
+ Link,
+ Paragraph,
+} from '@react-email/editor/extensions';
+
+const extensions = [Body, Paragraph, Heading, Bold, Italic, Link];
+```
+
+
+ When using individual extensions, you're responsible for including all the extensions
+ your content needs. Missing extensions will cause those content types to be dropped.
+
+
+## Extension categories
+
+
+
+
+ Extensions that create block-level content:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `Body` | Email body wrapper |
+ | `Section` | Content section |
+ | `Div` | Generic div container |
+ | `Paragraph` | Text paragraph |
+ | `Heading` | Heading levels 1–6 |
+ | `Blockquote` | Block quote |
+ | `CodeBlockPrism` | Code block with Prism syntax highlighting |
+ | `Divider` | Horizontal rule |
+ | `Button` | Email button element |
+ | `PreviewText` | Email preview text (shown in inbox list) |
+
+
+
+ List-related extensions:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `BulletList` | Unordered list |
+ | `OrderedList` | Ordered (numbered) list |
+ | `ListItem` | Individual list item |
+
+
+
+ Multi-column layout extensions:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `TwoColumns` | Two column layout |
+ | `ThreeColumns` | Three column layout |
+ | `FourColumns` | Four column layout |
+ | `ColumnsColumn` | Individual column within a layout |
+
+
+
+ Table-related extensions:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `Table` | Table container |
+ | `TableRow` | Table row |
+ | `TableCell` | Table cell |
+ | `TableHeader` | Table header cell |
+
+
+
+ Extensions that style inline text:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `Bold` | Bold text |
+ | `Italic` | Italic text |
+ | `Strike` | Strikethrough text |
+ | `Underline` | Underlined text |
+ | `Code` | Inline code |
+ | `Sup` | Superscript text |
+ | `Uppercase` | Uppercase transform |
+ | `Link` | Hyperlink (with `openOnClick: false` by default) |
+ | `Text` | Base text node |
+
+
+
+ Extensions that add HTML attributes to nodes:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `AlignmentAttribute` | Text alignment (left, center, right) |
+ | `StyleAttribute` | Inline CSS styles |
+ | `ClassAttribute` | CSS class names |
+
+
+
+ Helper extensions for editor behavior:
+
+ | Extension | Description |
+ |-----------|-------------|
+ | `Placeholder` | Placeholder text when content is empty |
+ | `PreservedStyle` | Preserves formatting when unlinking |
+ | `GlobalContent` | Stores metadata for serialization (CSS, etc.) |
+ | `MaxNesting` | Enforces maximum nesting depth |
+ | `HardBreak` | Line break within a block |
+
+
+
+
+For the full API reference with configurable options for each extension, see [Extensions Reference](/editor/api-reference/extensions-list).
diff --git a/apps/docs/editor/api-reference/extensions-list.mdx b/apps/docs/editor/api-reference/extensions-list.mdx
new file mode 100644
index 0000000000..ab3f47c490
--- /dev/null
+++ b/apps/docs/editor/api-reference/extensions-list.mdx
@@ -0,0 +1,377 @@
+---
+title: "Extensions Reference"
+sidebarTitle: "Extensions List"
+description: "Complete reference of all built-in editor extensions."
+icon: "list"
+---
+
+All extensions are available from `@react-email/editor/extensions` and are included
+in `StarterKit` by default. Each can be configured via `StarterKit.configure()` or
+imported individually.
+
+
+
+
+
+### Body
+
+Email body wrapper element.
+
+
+ Extra HTML attributes applied to the body element.
+
+
+---
+
+### Section
+
+Content section container.
+
+
+ Extra HTML attributes applied to the section element.
+
+
+---
+
+### Div
+
+Generic div container.
+
+
+ Extra HTML attributes applied to the div element.
+
+
+---
+
+### Paragraph
+
+Text paragraph.
+
+
+ Extra HTML attributes applied to the paragraph element.
+
+
+---
+
+### Heading
+
+Heading levels 1–6.
+
+
+ Which heading levels to enable.
+
+
+
+ Extra HTML attributes applied to the heading element.
+
+
+---
+
+### Blockquote
+
+Block quote element.
+
+
+ Extra HTML attributes applied to the blockquote element.
+
+
+---
+
+### CodeBlockPrism
+
+Code block with Prism.js syntax highlighting.
+
+
+ Default language for syntax highlighting.
+
+
+
+ Extra HTML attributes applied to the code block element.
+
+
+---
+
+### Divider
+
+Horizontal rule / separator.
+
+
+ Extra HTML attributes applied to the hr element.
+
+
+---
+
+### Button
+
+Email button (styled anchor element).
+
+
+ Extra HTML attributes applied to the button element.
+
+
+---
+
+### PreviewText
+
+Email preview text (shown in inbox list views).
+
+
+
+
+
+### BulletList
+
+Unordered list.
+
+
+ Extra HTML attributes applied to the ul element.
+
+
+---
+
+### OrderedList
+
+Ordered (numbered) list.
+
+
+ Extra HTML attributes applied to the ol element.
+
+
+---
+
+### ListItem
+
+Individual list item.
+
+
+ Extra HTML attributes applied to the li element.
+
+
+
+
+
+
+### TwoColumns
+
+Two column layout container. Inserts two `ColumnsColumn` children.
+
+---
+
+### ThreeColumns
+
+Three column layout container. Inserts three `ColumnsColumn` children.
+
+---
+
+### FourColumns
+
+Four column layout container. Inserts four `ColumnsColumn` children.
+
+---
+
+### ColumnsColumn
+
+Individual column within a column layout. Automatically created by column insertion commands.
+
+
+
+
+
+### Table
+
+Table container.
+
+
+ Extra HTML attributes applied to the table element.
+
+
+---
+
+### TableRow
+
+Table row.
+
+
+ Extra HTML attributes applied to the tr element.
+
+
+---
+
+### TableCell
+
+Table cell.
+
+
+ Extra HTML attributes applied to the td element.
+
+
+---
+
+### TableHeader
+
+Table header cell.
+
+
+
+
+
+### Bold
+
+Bold text formatting.
+
+
+ Extra HTML attributes applied to the strong element.
+
+
+---
+
+### Italic
+
+Italic text formatting.
+
+
+ Extra HTML attributes applied to the em element.
+
+
+---
+
+### Strike
+
+Strikethrough text formatting.
+
+
+ Extra HTML attributes applied to the s element.
+
+
+---
+
+### Underline
+
+Underline text formatting.
+
+
+ Extra HTML attributes applied to the u element.
+
+
+---
+
+### Code
+
+Inline code formatting.
+
+
+ Extra HTML attributes applied to the code element.
+
+
+---
+
+### Sup
+
+Superscript text.
+
+
+ Extra HTML attributes applied to the sup element.
+
+
+---
+
+### Uppercase
+
+Uppercase text transform.
+
+---
+
+### Link
+
+Hyperlink with extended email attributes.
+
+
+ Whether clicking a link navigates to the URL. Disabled by default in the editor.
+
+
+
+ Extra HTML attributes applied to the a element.
+
+
+---
+
+### Text
+
+Base text node. Required for any text content.
+
+
+
+
+
+### AlignmentAttribute
+
+Adds text alignment support (left, center, right) to block nodes.
+
+
+ Node types that support alignment.
+
+
+---
+
+### StyleAttribute
+
+Adds inline CSS style support to nodes.
+
+---
+
+### ClassAttribute
+
+Adds CSS class name support to nodes.
+
+
+
+
+
+### Placeholder
+
+Shows placeholder text when the editor is empty.
+
+
+ Placeholder text or function returning placeholder text.
+
+
+
+ Whether to show placeholders in child nodes.
+
+
+---
+
+### PreservedStyle
+
+Preserves inline formatting when unlinking text.
+
+---
+
+### GlobalContent
+
+Stores metadata (like custom CSS) that persists in the document but isn't visible.
+Used by the serializer to inject global styles.
+
+---
+
+### MaxNesting
+
+Enforces a maximum nesting depth for the document.
+
+
+ Maximum allowed nesting depth.
+
+
+
+ Node types to enforce the nesting limit on.
+
+
+---
+
+### HardBreak
+
+Line break within a block (Shift+Enter).
+
+
+
+
diff --git a/apps/docs/editor/api-reference/theming-api.mdx b/apps/docs/editor/api-reference/theming-api.mdx
new file mode 100644
index 0000000000..2943b02e4a
--- /dev/null
+++ b/apps/docs/editor/api-reference/theming-api.mdx
@@ -0,0 +1,205 @@
+---
+title: "Theming API"
+sidebarTitle: "Theming API"
+description: "Types and utilities for the email theming system."
+icon: "swatchbook"
+---
+
+## EmailTheming extension
+
+The `EmailTheming` extension from `@react-email/editor/plugins` adds theme-aware styling
+to the editor and provides a `SerializerPlugin` for theme-aware HTML export.
+
+```tsx
+import { EmailTheming } from '@react-email/editor/plugins';
+
+const extensions = [StarterKit, EmailTheming.configure({ theme: 'basic' })];
+```
+
+
+ The active theme. Built-in options: `'basic'` or `'minimal'`.
+
+
+## Types
+
+### EditorTheme
+
+```tsx
+type EditorTheme = 'basic' | 'minimal';
+```
+
+---
+
+### KnownThemeComponents
+
+All component keys that a theme can style:
+
+```tsx
+type KnownThemeComponents =
+ | 'reset'
+ | 'body'
+ | 'container'
+ | 'h1'
+ | 'h2'
+ | 'h3'
+ | 'paragraph'
+ | 'list'
+ | 'listItem'
+ | 'listParagraph'
+ | 'nestedList'
+ | 'blockquote'
+ | 'codeBlock'
+ | 'inlineCode'
+ | 'link'
+ | 'button'
+ | 'section'
+ | 'footer'
+ | 'hr'
+ | 'image';
+```
+
+---
+
+### KnownCssProperties
+
+CSS properties that can be customized per theme component:
+
+**Layout:** `align`, `width`, `height`, `padding`, `paddingTop`, `paddingRight`,
+`paddingBottom`, `paddingLeft`, `margin`, `marginTop`, `marginRight`, `marginBottom`,
+`marginLeft`
+
+**Appearance:** `backgroundColor`, `color`, `borderRadius`, `borderWidth`, `borderColor`,
+`borderStyle`
+
+**Typography:** `fontSize`, `fontWeight`, `lineHeight`, `textDecoration`, `fontFamily`,
+`letterSpacing`
+
+---
+
+### CssJs
+
+A mapping of theme component keys to CSS properties:
+
+```tsx
+type CssJs = Partial>;
+```
+
+---
+
+### PanelGroup
+
+Represents a group of configurable theme properties for a component:
+
+```tsx
+interface PanelGroup {
+ id: string;
+ title: string;
+ headerSlot?: React.ReactNode;
+ classReference?: string;
+ inputs: PanelInputProperty[];
+}
+```
+
+---
+
+### PanelInputProperty
+
+An individual configurable CSS property within a panel:
+
+```tsx
+interface PanelInputProperty {
+ label: string;
+ type: 'text' | 'number' | 'color' | 'select';
+ value: string;
+ prop: string;
+ classReference: string;
+ unit?: string;
+ options?: { label: string; value: string }[];
+ placeholder?: string;
+ category?: string;
+}
+```
+
+## Utility functions
+
+### getMergedCssJs
+
+Merges the base theme styles with custom panel overrides:
+
+```tsx
+import { getMergedCssJs } from '@react-email/editor/plugins';
+
+const mergedStyles = getMergedCssJs(theme, panelStyles);
+```
+
+
+ The active theme to use as a base.
+
+
+
+ Custom style overrides from the theme panel UI.
+
+
+---
+
+### getResolvedNodeStyles
+
+Resolves the final CSS styles for a specific node based on theme and document depth:
+
+```tsx
+import { getResolvedNodeStyles } from '@react-email/editor/plugins';
+
+const styles = getResolvedNodeStyles(node, depth, mergedCssJs);
+```
+
+
+ The TipTap node to resolve styles for.
+
+
+
+ The node's depth in the document tree. Affects styles for nested elements (e.g., nested lists).
+
+
+
+ The merged theme styles from `getMergedCssJs`.
+
+
+---
+
+### getThemeComponentKey
+
+Maps a node type and depth to a theme component key:
+
+```tsx
+import { getThemeComponentKey } from '@react-email/editor/plugins';
+
+const paragraphKey = getThemeComponentKey('paragraph', 0); // 'paragraph'
+const nestedListKey = getThemeComponentKey('bulletList', 2); // 'nestedList'
+```
+
+---
+
+### stylesToCss
+
+Converts a theme's style definitions to a CSS object:
+
+```tsx
+import { stylesToCss } from '@react-email/editor/plugins';
+
+const css = stylesToCss(styles, theme);
+```
+
+---
+
+### useEmailTheming
+
+React hook that provides access to the current theme state from within the editor:
+
+```tsx
+import { useEmailTheming } from '@react-email/editor/plugins';
+
+function MyComponent() {
+ const theming = useEmailTheming(editor);
+ // Access theming.styles, theming.theme, theming.css
+}
+```
diff --git a/apps/docs/editor/api-reference/ui-components.mdx b/apps/docs/editor/api-reference/ui-components.mdx
new file mode 100644
index 0000000000..a8f3e9f14f
--- /dev/null
+++ b/apps/docs/editor/api-reference/ui-components.mdx
@@ -0,0 +1,260 @@
+---
+title: "UI Components"
+sidebarTitle: "UI Components"
+description: "Complete reference for all editor UI components."
+icon: "window"
+---
+
+## BubbleMenu
+
+Floating formatting toolbar that appears on text selection.
+
+### BubbleMenu.Default
+
+Pre-configured bubble menu with all formatting options.
+
+```tsx
+import { BubbleMenu } from '@react-email/editor/ui';
+
+
+```
+
+
+ Items to hide from the menu. Options: `'bold'`, `'italic'`, `'underline'`,
+ `'strike'`, `'code'`, `'uppercase'`, `'align-left'`, `'align-center'`,
+ `'align-right'`, `'node-selector'`, `'link-selector'`.
+
+
+
+ Node types that should NOT trigger the bubble menu (e.g., `'codeBlock'`, `'button'`).
+
+
+
+ Mark types that should NOT trigger the bubble menu (e.g., `'link'`).
+
+
+
+ Position relative to the selection.
+
+
+
+ Distance from the selection in pixels.
+
+
+
+ Called when the bubble menu is hidden.
+
+
+
+ Additional CSS class on the wrapper element.
+
+
+---
+
+### BubbleMenu.Root
+
+Base container for building custom bubble menus.
+
+```tsx
+
+ {children}
+
+```
+
+Accepts the same props as `Default` except `excludeItems`.
+
+---
+
+### BubbleMenu.ItemGroup
+
+Visual grouping of bubble menu items.
+
+```tsx
+
+
+
+
+```
+
+---
+
+### Individual items
+
+| Component | Description |
+|-----------|-------------|
+| `BubbleMenu.Bold` | Bold toggle |
+| `BubbleMenu.Italic` | Italic toggle |
+| `BubbleMenu.Underline` | Underline toggle |
+| `BubbleMenu.Strike` | Strikethrough toggle |
+| `BubbleMenu.Code` | Inline code toggle |
+| `BubbleMenu.Uppercase` | Uppercase toggle |
+| `BubbleMenu.AlignLeft` | Left alignment |
+| `BubbleMenu.AlignCenter` | Center alignment |
+| `BubbleMenu.AlignRight` | Right alignment |
+| `BubbleMenu.Separator` | Visual separator |
+
+---
+
+### BubbleMenu.NodeSelector
+
+Dropdown to change the block type (paragraph, heading, etc.).
+
+
+ Controlled open state.
+
+
+
+ Called when the dropdown opens or closes.
+
+
+---
+
+### BubbleMenu.LinkSelector
+
+Popover for adding and editing links.
+
+
+ Controlled open state.
+
+
+
+ Called when the popover opens or closes.
+
+
+---
+
+## LinkBubbleMenu
+
+Contextual menu that appears when clicking a link.
+
+### LinkBubbleMenu.Default
+
+Pre-configured link bubble menu with edit, open, and unlink actions.
+
+```tsx
+import { LinkBubbleMenu } from '@react-email/editor/ui';
+
+
+```
+
+---
+
+## ButtonBubbleMenu
+
+Contextual menu that appears when clicking an email button.
+
+### ButtonBubbleMenu.Default
+
+Pre-configured button bubble menu with edit link and unlink actions.
+
+```tsx
+import { ButtonBubbleMenu } from '@react-email/editor/ui';
+
+
+```
+
+### ButtonBubbleMenu.Form
+
+Inline form for editing the button's link URL.
+
+### ButtonBubbleMenu.Unlink
+
+Button to remove the link from an email button.
+
+---
+
+## ImageBubbleMenu
+
+Contextual menu that appears when clicking an image.
+
+### ImageBubbleMenu.Default
+
+Pre-configured image bubble menu.
+
+```tsx
+import { ImageBubbleMenu } from '@react-email/editor/ui';
+
+
+```
+
+---
+
+## SlashCommand
+
+Command menu triggered by typing a character (default: `/`).
+
+### SlashCommand.Root
+
+```tsx
+import { defaultSlashCommands, SlashCommand } from '@react-email/editor/ui';
+
+
+```
+
+
+ Array of commands to display. Falls back to `defaultSlashCommands` when not provided.
+
+
+
+ Custom filter function. By default, uses fuzzy matching on title, description, and search terms.
+
+
+
+ Character that triggers the command menu.
+
+
+
+ Controls when the menu can appear. Return `false` to prevent it.
+
+
+
+ Custom render function for the command list. Receives `items`, `query`, `selectedIndex`, and `onSelect`.
+
+
+### SlashCommandItem
+
+```tsx
+interface SlashCommandItem {
+ title: string;
+ description: string;
+ searchTerms?: string[];
+ icon: ReactNode;
+ category: string;
+ command: (props: { editor: Editor; range: Range }) => void;
+}
+```
+
+### SlashCommandRenderProps
+
+```tsx
+interface SlashCommandRenderProps {
+ items: SlashCommandItem[];
+ query: string;
+ selectedIndex: number;
+ onSelect: (index: number) => void;
+}
+```
+
+---
+
+## CSS imports
+
+Each UI component has a corresponding CSS file:
+
+| Component | CSS import |
+|-----------|------------|
+| BubbleMenu | `@react-email/editor/styles/bubble-menu.css` |
+| LinkBubbleMenu | `@react-email/editor/styles/link-bubble-menu.css` |
+| ButtonBubbleMenu | `@react-email/editor/styles/button-bubble-menu.css` |
+| ImageBubbleMenu | `@react-email/editor/styles/image-bubble-menu.css` |
+| SlashCommand | `@react-email/editor/styles/slash-command.css` |
+| Theme (colors) | `@react-email/editor/themes/default.css` |
+
+
+ `@react-email/editor/themes/default.css` bundles all of the above CSS files into a single
+ import. Unless you need to cherry-pick specific styles, this is all you need:
+
+ ```tsx
+ import '@react-email/editor/themes/default.css';
+ ```
+
diff --git a/apps/docs/editor/api-reference/use-editor.mdx b/apps/docs/editor/api-reference/use-editor.mdx
new file mode 100644
index 0000000000..3b015c91ce
--- /dev/null
+++ b/apps/docs/editor/api-reference/use-editor.mdx
@@ -0,0 +1,122 @@
+---
+title: "useEditor"
+sidebarTitle: "useEditor"
+description: "Hook for creating and managing an editor instance."
+icon: "hook"
+---
+
+## Import
+
+```tsx
+import { useEditor } from '@react-email/editor/core';
+```
+
+## Parameters
+
+
+ Initial editor content. Accepts TipTap JSON or an HTML string.
+ Ignored when collaboration extensions are detected (the collab provider manages content).
+
+
+
+ Additional extensions to load. `StarterKit` is always included automatically,
+ so you only need to pass extra extensions (e.g., `EmailTheming`, custom nodes).
+
+
+
+ Called on every content change. The `transaction` object provides metadata about the change.
+
+
+
+ Custom paste handler. Return `true` to prevent default paste behavior.
+
+
+
+ Called when an image is pasted or dropped. Use this to upload the image and insert
+ the resulting URL.
+
+
+
+ Called once when the editor has finished initializing.
+
+
+
+ Toggle read-only mode. When `false`, the editor content cannot be modified.
+
+
+Any additional [TipTap `UseEditorOptions`](https://tiptap.dev/docs/editor/api/editor#settings)
+are also accepted and forwarded to the underlying TipTap editor.
+
+## Return value
+
+
+ The TipTap editor instance. `null` until the editor is mounted.
+
+
+
+ Whether the document is visually empty (only contains an empty paragraph).
+ Excludes `globalContent` nodes from the calculation.
+
+
+
+ The effective extensions array, including `StarterKit` and `UndoRedo` (unless collaborative).
+
+
+
+ Content validation error, if the provided content is invalid for the current schema.
+ When set, the editor is automatically made read-only.
+
+
+
+ Whether collaboration extensions (Liveblocks, Y.js) were detected in the extensions array.
+
+
+## Collaboration support
+
+
+ When collaboration extensions are detected (`liveblocksExtension` or `collaboration`), the
+ hook automatically:
+ - Ignores the `content` parameter (content is managed by the collab provider)
+ - Excludes the `UndoRedo` extension (collab extensions handle their own history)
+
+
+## Example
+
+Use `useEditor` directly when you need more control than `EditorProvider` offers:
+
+```tsx
+import { useEditor } from '@react-email/editor/core';
+
+export function MyEditor() {
+ const { editor, isEditorEmpty, contentError } = useEditor({
+ content: { type: 'doc', content: [] },
+ onUpdate: (editor) => {
+ console.log('Content changed:', editor.getJSON());
+ },
+ onReady: (editor) => {
+ console.log('Editor ready');
+ },
+ });
+
+ if (contentError) {
+ return Error: {contentError.message}
;
+ }
+
+ if (!editor) {
+ return Loading...
;
+ }
+
+ return (
+