Domternal - Rich Text Editor for Angular, React, Vue & Vanilla JS

· Domternal

5 min read Original article ↗

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)

Get started →

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.