From 14600e2f2d643dd3eb0724683d377f47f0f350fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Fri, 20 Mar 2026 12:35:26 -0300 Subject: [PATCH 1/3] Add comprehensive editor documentation - Add advanced, feature, and API reference docs for the editor - Document custom extensions, theming, events, and UI components - Add getting started and next-step snippets --- .../editor/advanced/custom-extensions.mdx | 269 +++++++++++++ apps/docs/editor/advanced/event-bus.mdx | 102 +++++ apps/docs/editor/advanced/extensions.mdx | 159 ++++++++ .../editor/api-reference/extensions-list.mdx | 381 ++++++++++++++++++ .../docs/editor/api-reference/theming-api.mdx | 205 ++++++++++ .../editor/api-reference/ui-components.mdx | 251 ++++++++++++ apps/docs/editor/api-reference/use-editor.mdx | 122 ++++++ apps/docs/editor/features/bubble-menu.mdx | 141 +++++++ apps/docs/editor/features/buttons.mdx | 84 ++++ apps/docs/editor/features/column-layouts.mdx | 116 ++++++ apps/docs/editor/features/email-export.mdx | 181 +++++++++ apps/docs/editor/features/link-editing.mdx | 74 ++++ apps/docs/editor/features/slash-commands.mdx | 156 +++++++ apps/docs/editor/features/theming.mdx | 120 ++++++ apps/docs/editor/getting-started.mdx | 157 ++++++++ apps/docs/editor/overview.mdx | 58 +++ apps/docs/snippets/editor-install.mdx | 19 + apps/docs/snippets/editor-next-steps.mdx | 14 + 18 files changed, 2609 insertions(+) create mode 100644 apps/docs/editor/advanced/custom-extensions.mdx create mode 100644 apps/docs/editor/advanced/event-bus.mdx create mode 100644 apps/docs/editor/advanced/extensions.mdx create mode 100644 apps/docs/editor/api-reference/extensions-list.mdx create mode 100644 apps/docs/editor/api-reference/theming-api.mdx create mode 100644 apps/docs/editor/api-reference/ui-components.mdx create mode 100644 apps/docs/editor/api-reference/use-editor.mdx create mode 100644 apps/docs/editor/features/bubble-menu.mdx create mode 100644 apps/docs/editor/features/buttons.mdx create mode 100644 apps/docs/editor/features/column-layouts.mdx create mode 100644 apps/docs/editor/features/email-export.mdx create mode 100644 apps/docs/editor/features/link-editing.mdx create mode 100644 apps/docs/editor/features/slash-commands.mdx create mode 100644 apps/docs/editor/features/theming.mdx create mode 100644 apps/docs/editor/getting-started.mdx create mode 100644 apps/docs/editor/overview.mdx create mode 100644 apps/docs/snippets/editor-install.mdx create mode 100644 apps/docs/snippets/editor-next-steps.mdx diff --git a/apps/docs/editor/advanced/custom-extensions.mdx b/apps/docs/editor/advanced/custom-extensions.mdx new file mode 100644 index 0000000000..10b03d923e --- /dev/null +++ b/apps/docs/editor/advanced/custom-extensions.mdx @@ -0,0 +1,269 @@ +--- +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]; + +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..3ff6a29b95 --- /dev/null +++ b/apps/docs/editor/api-reference/extensions-list.mdx @@ -0,0 +1,381 @@ +--- +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. + + + Whether columns can be resized. + + + + 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..0665114ec7 --- /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 key = getThemeComponentKey('paragraph', 0); // 'paragraph' +const key = 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..d7b1407954 --- /dev/null +++ b/apps/docs/editor/api-reference/ui-components.mdx @@ -0,0 +1,251 @@ +--- +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. + + + + 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` | 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 ( +
+
el && editor.setOptions({ element: el })} /> + {isEditorEmpty &&

Start typing...

} +
+ ); +} +``` + + + For most use cases, `EditorProvider` from `@tiptap/react` is simpler. + Use `useEditor` when you need direct access to the editor instance before rendering, + or when integrating with complex state management. + diff --git a/apps/docs/editor/features/bubble-menu.mdx b/apps/docs/editor/features/bubble-menu.mdx new file mode 100644 index 0000000000..f56d226c9d --- /dev/null +++ b/apps/docs/editor/features/bubble-menu.mdx @@ -0,0 +1,141 @@ +--- +title: "Bubble Menu" +sidebarTitle: "Bubble Menu" +description: "Add floating formatting toolbars that appear on text selection." +icon: "message-lines" +--- + +## Quick start + +Add `BubbleMenu.Default` as a child of `EditorProvider` to get a fully-featured formatting toolbar: + +```tsx +import { StarterKit } from '@react-email/editor/extensions'; +import { BubbleMenu } from '@react-email/editor/ui'; +import { EditorProvider } from '@tiptap/react'; +import '@react-email/editor/styles/bubble-menu.css'; +import '@react-email/editor/themes/default.css'; + +const extensions = [StarterKit]; + +export function MyEditor() { + return ( + + + + ); +} +``` + +Select text to see the toolbar with formatting, alignment, node type selection, and link controls. + +## Excluding items + +Hide specific items from the default bubble menu using `excludeItems`: + +```tsx + +``` + +All excludable item keys: + +| Key | Description | +|-----|-------------| +| `bold` | Bold toggle | +| `italic` | Italic toggle | +| `underline` | Underline toggle | +| `strike` | Strikethrough toggle | +| `code` | Inline code toggle | +| `uppercase` | Uppercase toggle | +| `align-left` | Left alignment | +| `align-center` | Center alignment | +| `align-right` | Right alignment | +| `node-selector` | Block type selector (paragraph, headings, etc.) | +| `link-selector` | Link add/edit control | + +## Excluding nodes and marks + +Prevent the bubble menu from showing on certain node types or when certain marks are active: + +```tsx + +``` + +This is especially useful when combining the text bubble menu with other contextual menus: + +```tsx +{/* Hide text bubble menu on links and buttons — their own menus handle those */} + + + +``` + +## Composing from primitives + +For full control, build a custom bubble menu using the compound component API: + +```tsx +import { StarterKit } from '@react-email/editor/extensions'; +import { BubbleMenu } from '@react-email/editor/ui'; +import { EditorProvider } from '@tiptap/react'; + +export function MyEditor() { + return ( + + + + + + + + + + + + + + + ); +} +``` + +The pattern is: `BubbleMenu.Root` wraps everything, `BubbleMenu.ItemGroup` creates visual groupings, +and individual items render the toggle buttons. + +## Available 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.NodeSelector` | Block type dropdown (paragraph, h1–h3, etc.) | +| `BubbleMenu.LinkSelector` | Link add/edit popover | +| `BubbleMenu.Separator` | Visual separator between groups | + +## Placement and offset + +Control where the bubble menu appears relative to the selection: + +```tsx + + {/* items */} + +``` + + + Whether the menu appears above or below the selection. + + + + Distance from the selection in pixels. + diff --git a/apps/docs/editor/features/buttons.mdx b/apps/docs/editor/features/buttons.mdx new file mode 100644 index 0000000000..b654517930 --- /dev/null +++ b/apps/docs/editor/features/buttons.mdx @@ -0,0 +1,84 @@ +--- +title: "Buttons" +sidebarTitle: "Buttons" +description: "Add and edit email buttons with the button bubble menu." +icon: "rectangle-wide" +--- + +## Quick start + +Add `ButtonBubbleMenu.Default` and a slash command for inserting buttons: + +```tsx +import { StarterKit } from '@react-email/editor/extensions'; +import { BUTTON, ButtonBubbleMenu, SlashCommand } from '@react-email/editor/ui'; +import { EditorProvider } from '@tiptap/react'; +import '@react-email/editor/styles/button-bubble-menu.css'; +import '@react-email/editor/themes/default.css'; + +const extensions = [StarterKit]; + +const content = ` +

Click the button below to see its bubble menu.

+ +

Use the slash command menu (type /) to insert more buttons.

+`; + +export function MyEditor() { + return ( + + + + + ); +} +``` + +## Button content format + +Buttons in the editor are represented as styled anchor elements: + +```html + +``` + +The wrapping `div` controls alignment (left, center, or right), while the `` tag renders +as a styled button in the editor and serializes to a React Email ` + ); +} +``` diff --git a/apps/docs/editor/features/column-layouts.mdx b/apps/docs/editor/features/column-layouts.mdx new file mode 100644 index 0000000000..eab50fbf13 --- /dev/null +++ b/apps/docs/editor/features/column-layouts.mdx @@ -0,0 +1,116 @@ +--- +title: "Column Layouts" +sidebarTitle: "Column Layouts" +description: "Insert responsive multi-column layouts." +icon: "columns-3" +--- + +## Quick start + +Column extensions are included in `StarterKit` by default — no extra imports needed: + +```tsx +import { StarterKit } from '@react-email/editor/extensions'; +import { EditorProvider } from '@tiptap/react'; + +const extensions = [StarterKit]; + +export function MyEditor() { + return ; +} +``` + +## Inserting columns programmatically + +Use the `insertColumns` command to add a column layout: + +```tsx +// Insert a 2-column layout +editor.chain().focus().insertColumns(2).run(); + +// Insert a 3-column layout +editor.chain().focus().insertColumns(3).run(); + +// Insert a 4-column layout +editor.chain().focus().insertColumns(4).run(); +``` + +## Building a toolbar + +Here's a complete example with a toolbar for inserting column layouts, using the `slotBefore` +prop to position it above the editor: + +```tsx +import { StarterKit } from '@react-email/editor/extensions'; +import { EditorProvider, useCurrentEditor } from '@tiptap/react'; +import { Columns2, Columns3, Columns4 } from 'lucide-react'; + +const extensions = [StarterKit]; + +function ToolbarButton({ + label, + icon, + onClick, +}: { + label: string; + icon?: React.ReactNode; + onClick: () => void; +}) { + return ( + + ); +} + +function Toolbar() { + const { editor } = useCurrentEditor(); + if (!editor) return null; + + return ( +
+ } + onClick={() => editor.chain().focus().insertColumns(2).run()} + /> + } + onClick={() => editor.chain().focus().insertColumns(3).run()} + /> + } + onClick={() => editor.chain().focus().insertColumns(4).run()} + /> +
+ ); +} + +export function MyEditor() { + return ( + } + /> + ); +} +``` + + + Use `slotBefore` to render custom UI above the editor, and `useCurrentEditor()` to access + the editor instance from child components. + + +## Via slash commands + +The default slash commands already include column layouts. Type `/` and search for "columns": + +- **Two Columns** — `TWO_COLUMNS` +- **Three Columns** — `THREE_COLUMNS` +- **Four Columns** — `FOUR_COLUMNS` + +See [Slash Commands](/editor/features/slash-commands) for setup details. diff --git a/apps/docs/editor/features/email-export.mdx b/apps/docs/editor/features/email-export.mdx new file mode 100644 index 0000000000..38b95f9101 --- /dev/null +++ b/apps/docs/editor/features/email-export.mdx @@ -0,0 +1,181 @@ +--- +title: "Email Export" +sidebarTitle: "Email Export" +description: "Convert editor content to email-ready HTML." +icon: "file-export" +--- + +## Quick start + +Use `composeReactEmail` to convert editor content into email-ready HTML and plain text: + +```tsx +import { composeReactEmail } from '@react-email/editor/core'; +import { StarterKit } from '@react-email/editor/extensions'; +import { EmailTheming } from '@react-email/editor/plugins'; +import { BubbleMenu } from '@react-email/editor/ui'; +import { EditorProvider, useCurrentEditor } from '@tiptap/react'; +import { useState } from 'react'; + +const extensions = [StarterKit, EmailTheming]; + +function ExportPanel() { + const { editor } = useCurrentEditor(); + const [html, setHtml] = useState(''); + const [exporting, setExporting] = useState(false); + + const handleExport = async () => { + if (!editor) return; + setExporting(true); + const result = await composeReactEmail({ editor, preview: null }); + setHtml(result.html); + setExporting(false); + }; + + return ( +
+ + {html && ( +