The Rich Text Editor
your framework
deserves
Classic and Notion-style editors out of the box.
Or go headless and assemble from 65+ tree-shakeable extensions.
First-class wrappers for Angular, React, Vue, and Vanilla.
Everything you need to build
rich editing experiences
A complete editor framework with full control over every aspect of your editor.
65+ Built-in Extensions
23 nodes, 9 marks, 27 extensions across 15 tree-shakeable packages. Tables with merge, images, emoji, mentions, details, syntax highlighting, Notion-style blocks, and table of contents.
Angular, React, Vue & Vanilla Wrappers
Drop-in editor, toolbar, bubble menu, floating menu, emoji picker, and Notion color picker. Or skip them - use the headless core with any framework.
Built-in Toolbar & Theme
Each extension contributes toolbar items via addToolbarItems(). 49 Phosphor icons. Light and dark themes via CSS custom properties - restyle with one declaration.
Styled HTML Export
getHTML({ styled: true }) outputs inline CSS that survives email clients, CMS rich-text fields, and Google Docs paste. No external stylesheet required.
Markdown + Slash Commands
Type ## for headings, **bold**, > for blockquote, --- for divider, or / to open the slash menu. Input rules registered by every extension.
Notion-style Block UX
Hover to grab a block, drag to reorder. Right-click for duplicate, turn into, color, or copy link. Tab to indent. Toggle the mode on with .dm-notion-mode.
Every extension you need.
Batteries included: from basic formatting to advanced features.
65+ extensions across 15 packages. All MIT licensed.
View All Packages →Built for production
Architecture decisions that make the difference at scale.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
A11y Built In
ARIA roles on every node, full keyboard navigation, screen reader tested. Tab through the toolbar, Enter to activate, Esc to close menus. WCAG-friendly by default.
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
SSR Ready
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
Get started
in minutes.
A few lines of code is all you need. Pick your framework.
npm install @domternal/core @domternal/theme @domternal/vanilla import { StarterKit, BubbleMenu } from '@domternal/core'; import { DomternalEditor, DomternalToolbar, DomternalBubbleMenu, } from '@domternal/vanilla'; import '@domternal/theme'; const editorEl = document.getElementById('editor')!; const toolbarEl = document.getElementById('toolbar')!; const bubbleEl = document.getElementById('bubble')!; const dm = new DomternalEditor(editorEl, { extensions: [StarterKit, BubbleMenu.configure({ element: bubbleEl })], content: '<p>Hello from Vanilla!</p>', }); new DomternalToolbar(toolbarEl, { editor: dm.editor }); new DomternalBubbleMenu(bubbleEl, { editor: dm.editor });
import { Editor, Document, Text, Paragraph, Bold, Italic, Underline, } from '@domternal/core'; new Editor({ element: document.getElementById('editor')!, extensions: [Document, Text, Paragraph, Bold, Italic, Underline], content: '<p>Hello <strong>Bold</strong>, <em>Italic</em> and <u>Underline</u>!</p>', });
import { Component, signal } from '@angular/core'; import { DomternalEditorComponent, DomternalToolbarComponent, DomternalBubbleMenuComponent, } from '@domternal/angular'; import { Editor, StarterKit, BubbleMenu } from '@domternal/core'; @Component({ selector: 'app-editor', imports: [DomternalEditorComponent, DomternalToolbarComponent, DomternalBubbleMenuComponent], template: ` @if (editor(); as ed) { <domternal-toolbar [editor]="ed" /> } <domternal-editor [extensions]="extensions" [content]="content" (editorCreated)="editor.set($event)" /> @if (editor(); as ed) { <domternal-bubble-menu [editor]="ed" /> } ` }) export class EditorComponent { editor = signal<Editor | null>(null); extensions = [StarterKit, BubbleMenu]; content = '<p>Hello from Angular!</p>'; }
import { Domternal } from '@domternal/react'; import { StarterKit, BubbleMenu } from '@domternal/core'; export default function Editor() { return ( <Domternal extensions={[StarterKit, BubbleMenu]} content="<p>Hello from React!</p>" > <Domternal.Toolbar /> <Domternal.Content /> <Domternal.BubbleMenu /> </Domternal> ); }
<script setup> import { Domternal } from '@domternal/vue'; import { StarterKit, BubbleMenu } from '@domternal/core'; const extensions = [StarterKit, BubbleMenu]; </script> <template> <Domternal :extensions="extensions" content="<p>Hello from Vue!</p>" > <Domternal.Toolbar /> <Domternal.Content /> <Domternal.BubbleMenu /> </Domternal> </template>
Free forever.
No asterisk.
Everything in Open Source, plus:
Start building.
Ship something durable.
Open source. MIT licensed. Free forever.