The Rich Text Editor
your framework
deserves
Classic and Notion-style editors out of the box.
Or go headless and assemble from 70+ tree-shakeable extensions.
Drop-in components, one per framework
FEATURES
Everything you need to build
rich editing experiences
A complete editor framework with full control over every aspect of your editor.
Notion-style Block UX
Hover to grab a block, drag to reorder or nest. Open the block menu for duplicate, turn into, colour, or copy link. Type / to insert anything. Tab to indent. It is one class on the editor, not a separate product.
Notion mode
70+ Built-in Extensions
27 nodes, 9 marks and 37 extensions across 17 tree-shakeable packages: tables with merge, images, emoji, mentions, details, LaTeX math, syntax highlighting and Markdown in and out.
Markdown In & Out
Type ## for a heading, paste a whole Markdown document, and export back to GitHub-flavored Markdown.
Styled HTML Export
getHTML({ styled: true }) inlines the CSS, so it survives email clients, CMS fields and a paste into Google Docs.
Built-in Toolbar & Theme
Every extension contributes its own toolbar items through addToolbarItems(), so the toolbar assembles itself from whatever you enabled. 51 Phosphor icons, light and dark, restyled from CSS custom properties.
Classic mode
Four first-class wrappers
Drop-in components for Angular, React, Vue and Vanilla. Or skip them and drive the headless core yourself.
IN MOTION
The parts you only see
when you start typing.
Block dragging, the slash menu and Markdown paste, in the editor you can try above.
Drag a block anywhere
Grab the gutter handle and move a block, or drop it onto the edge of a list item to nest it. What lands keeps its own type: a checked task item is still a checked task item at the top of an ordered list.
Press / to insert anything
The slash menu opens where the caret is and filters as you type, showing each block type with its icon, description and Markdown shortcut. Enter inserts in place; you never leave the keyboard.
Paste Markdown, get rich text
A whole Markdown document converts on the way in: headings, bold, task lists with working checkboxes, fenced code with syntax highlighting, blockquotes. It exports back to GitHub-flavored Markdown too.
EXTENSION ECOSYSTEM
Every extension
you need.
Batteries included: from basic formatting to advanced features.
BoldItalicUnderlineStrikethroughCodeLinkSubscriptSuperscriptHighlightText Color
Font FamilyFont SizeLine HeightClear FormattingHeadingParagraphBlockquoteCode BlockSyntax HighlightingHorizontal Rule
Bullet ListOrdered ListTask ListTableImageEmojiMentionDetailsMathMarkdownHistoryText AlignTypographyPlaceholderCharacter CountBubble MenuFloating MenuDropcursorGapcursorTrailing NodeInvisible CharactersUnique IDStarter KitFocus
UNDER THE HOOD
Built for production
Architecture decisions that make the difference at scale.
01
Schema Conflict Detection
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
02
Extension Composability
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior.
03
Fluent Command API
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects.
04
XSS-Hardened URLs
Blocks javascript:, vbscript:, file: protocols on images and links across four layers: parseHTML, renderHTML, commands, and input rules.
05
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.
06
Zero-Jitter Floating UI
Zero JS during scroll events. Floating elements use position: absolute with @floating-ui/dom - the CSS compositor handles the rest.
07
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.
QUICK START
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>
PRICING
Free today.
Free after Pro ships.
Everything that is MIT licensed today stays MIT licensed. Pro ships as separate add-on packages on top. Nothing that is free today moves behind a paywall.
Open Source
Free forever, MIT
✓ Complete editor engine
✓ Angular, React, Vue & Vanilla components (editor, toolbar, menus)
✓ Notion-style block menu, slash command & Table of Contents
✓ Full tables: merge, resize, styling
✓ 70+ extensions across 17 packages
✓ Built-in toolbar & theme (light + dark)
✓ Image upload, emoji picker & mentions
✓ Custom node views (React & Vue)
COMING SOON
Pro
Add-ons on top of MIT
Pricing announced at launch.
Everything in Open Source, plus:
→ Real-time collaboration (Yjs/CRDT)
→ Inline comments & threads
→ Version history with author-attributed diff
→ AI writing assistant
→ Notion-style columns
→ Word (.docx) & PDF export
··· And more planned
Start building.
Ship something durable.
Open source. MIT licensed. Free forever.