Toolbar, bubble menu, tables, code blocks, image upload, and more. All included, all ready to use.
Or use the headless core to build your own UI, any framework.
npm i @domternal/core @domternal/angular @domternal/theme A fully interactive editor. Type, format, and explore every feature live.
A few lines of code is all you need
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 { Editor, StarterKit, defaultIcons } from '@domternal/core'; import '@domternal/theme'; const editorEl = document.getElementById('editor')!; // Toolbar const toolbar = document.createElement('div'); toolbar.className = 'dm-toolbar'; toolbar.innerHTML = `<div class="dm-toolbar-group"> <button class="dm-toolbar-button" data-mark="bold">${defaultIcons.textB}</button> <button class="dm-toolbar-button" data-mark="italic">${defaultIcons.textItalic}</button> <button class="dm-toolbar-button" data-mark="underline">${defaultIcons.textUnderline}</button> </div>`; editorEl.before(toolbar); // Editor const editor = new Editor({ element: editorEl, extensions: [StarterKit], content: '<p>Hello world</p>', }); // Toggle marks on click toolbar.addEventListener('click', (e) => { const btn = (e.target as Element).closest<HTMLButtonElement>('[data-mark]'); if (!btn) return; editor.chain().focus().toggleMark(btn.dataset.mark!).run(); });
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>'; }
A complete editor framework with full control over every aspect of your editor.
23 nodes, 9 marks, 25 extensions across 10 packages. Tables, images, emoji, mentions, details, syntax highlighting. Everything included.
5 components: editor, toolbar, bubble menu, floating menu, emoji picker. Signals, OnPush, ControlValueAccessor. Built for modern Angular.
Every extension declares toolbar items via addToolbarItems(). 45 Phosphor icons included. Light & dark theme with CSS custom properties. Ready to customize.
getHTML({ styled: true }) produces inline CSS ready for email clients, CMS, and Google Docs. Customizable styles for tables, blockquotes, code blocks, and more.
Type ## for heading, > for blockquote, - for list, **bold**, ==highlight==, --- for rule. Every extension registers its own input rules.
2,675 unit tests across 92 files. 1,550 Playwright E2E tests across 34 specs. Every extension, mark, and node is tested.
| Feature | Q1 | Q2 | Q3 |
|---|---|---|---|
| Editor Core | Released | Stable | Stable |
| Tables | Released | Stable | Stable |
| Theme | Light + Dark (Merged Cell) | ||
| Angular | 5 Components | Signals | OnPush |
Batteries included: from basic formatting to advanced features.
23 nodes, 9 marks, 25 extensions across 10 packages. All MIT licensed.
View All Extensions →Architecture decisions that make the difference at scale.
Duplicate extension names throw a clear error at startup. No mysterious production bugs from silent overwrites.
create() → configure() → extend() with this.parent?.() to call the base version. Override any hook while preserving parent behavior. Fully typed.
editor.chain().focus().toggleBold().run() chains commands on a shared transaction. editor.can().toggleBold() dry-runs without side effects. Fully typed.
Blocks javascript:, vbscript:, file: protocols across four layers: parseHTML, renderHTML, commands, and input rules.
addGlobalAttributes() lets extensions inject attributes into node types they don't own. TextAlign, TextColor, FontSize, Highlight, and UniqueID all use this pattern. Zero coupling.
All floating elements use position: absolute inside the editor with @floating-ui/dom. CSS compositor handles scroll. Zero JS during scroll events.
Server-side rendering via linkedom. Generate HTML on the server without a browser. Works with Angular Universal, Astro, and any Node.js environment.
The core works with any framework. Angular components ship today. React and Vue coming soon.
Open source core, MIT licensed. Pro extensions coming soon for teams that need more.
Everything in Open Source, plus: