Domternal
A rich text editor toolkit built on ProseMirror, with a headless core and first-class Angular, React, Vue, and Vanilla components. Take the core alone and drive the DOM yourself, add the toolbar and theme for a finished editor, or mount the components for your framework. Notion-style block editing, drag handle, slash menu and floating outline are included. JavaScript exports are tree-shakeable; the optional theme ships as one complete CSS stylesheet.
Website · Getting Started · Packages & Bundle Size
Live examples - full editors for Angular, React, Vue, and Vanilla, editable in the browser
Features
- Headless core - use with any framework or vanilla JS/TS
- Angular components - editor, toolbar, bubble menu, floating menu, emoji picker, notion color picker (signals, OnPush, zoneless-ready)
- React components - composable
Domternalcomponent, toolbar, bubble menu, floating menu, emoji picker, notion color picker, custom node views (React 18+) - Vue components - composable
Domternalcomponent,useEditor/useEditorStatecomposables, toolbar, bubble menu, floating menu, emoji picker, notion color picker, custom node views (Vue 3.3+) - Vanilla wrapper - framework-free class-based API for Astro, Svelte, Solid, plain HTML, and Web Components - editor, toolbar, bubble menu, floating menu, emoji picker, notion color picker
- Notion-style block UX - drag-to-reorder, block context menu, slash command, smart paste, keyboard reorder, floating Table of Contents, from
@domternal/extension-block-controlsand@domternal/extension-toc, withpreset: 'notion'for the layout and preset-aware behavior - Print to paper or PDF -
printDocument()leaves your app's chrome off the page, with optional image/font preparation and eligible repeated table headers; the theme's paper layer also applies to Ctrl/Cmd+P - 70+ extensions across core and 11 extension packages - nodes, marks, and behavior extensions
- 130+ chainable commands -
editor.chain().focus().toggleBold().run() - Full table support - cell merging, column resize, row/column controls, cell toolbar and opt-in row heights, cell padding/borders and page-break preferences
- Optional image geometry: crop, rotate and mirror images without changing source bytes
- Editable document notes: opt-in footnote/endnote bodies and references in ordinary editor JSON, with navigation and undo
- Office paste cleanup: opt-in Word and Google Docs HTML normalization, preserve/adapt formatting, list reconstruction, and localized feedback about content the editor cannot keep
- Optional custom list labels: native counters, bounded compound templates, Unicode bullets and local PNG markers, with matching collaborative schemas and companion CSS for standalone HTML
- Tree-shakeable JavaScript - import only the JavaScript exports you use; the optional
@domternal/themedeliberately ships one complete stylesheet - ~95 KiB minified and gzipped (own code), ~176 KiB minified and gzipped total with runtime dependencies: see Packages for the full bundle breakdown
- TypeScript first - every package builds under
strict, withexactOptionalPropertyTypesandnoUncheckedIndexedAccess - 17,000+ automated test executions - unit coverage and a Playwright matrix across four demo apps
- Light and dark theme - 150+ CSS custom properties for full visual control
- Optional text contrast: improve literal text colors on supported backgrounds while preserving authored colors in stored content, HTML, clipboard output and print
- Per-editor UI localization: replace labels at creation or runtime, keep English fallback text, and opt into complete German catalogs
- Inline styles export -
getHTML({ styled: true })produces inline CSS ready for email clients, CMS, and Google Docs - Headless and SSR helpers -
createSchemafor shared editor/worker schemas, plusgenerateHTML,generateJSONandgenerateTextfor server-side conversion
Quick Start
Headless (Vanilla JS/TS)
pnpm add @domternal/core
import { Editor, Document, Text, Paragraph, Bold, Italic, Underline } from '@domternal/core';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [Document, Text, Paragraph, Bold, Italic, Underline],
content: '<p>Hello <strong>World</strong>!</p>',
});
The example above wires the minimum schema by hand to show the headless model; in practice use StarterKit for a batteries-included setup with headings, lists, code blocks, history, and more.
Getting Started Guide - headless core, themed UI with toolbar, and Angular/React/Vue component setup
Word and Google Docs paste
Add the optional @domternal/extension-paste-cleanup package alongside your editor:
pnpm add @domternal/extension-paste-cleanup
import { Editor, StarterKit } from '@domternal/core';
import { PasteCleanup } from '@domternal/extension-paste-cleanup';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [StarterKit, PasteCleanup.configure({ formatting: 'preserve' })],
});
Register the same extension in Angular, React, Vue, or Vanilla. preserve keeps
supported source formatting; adapt uses the receiving editor's formatting.
Register Table, Image, and other extensions for content your editor should keep.
The built-in notice and onPasteResult report losses, and the standalone /html
entry normalizes HTML without a DOM. PasteCleanup is not part of StarterKit or
the wrappers' default extensions. It handles clipboard HTML, not .docx files.
See the package README for options,
image handling, feedback, and source-specific limits.
Localization
Every editor starts with English UI text and accepts its own i18n configuration. German
catalogs are available as optional /locales/de imports from core and each extension that
owns UI messages. Merge only the catalogs for the features you use:
import type { I18nOptions } from '@domternal/core';
import {
deMessages as coreDe,
deSearchAliases as coreAliasesDe,
} from '@domternal/core/locales/de';
import {
deMessages as tableDe,
deSearchAliases as tableAliasesDe,
} from '@domternal/extension-table/locales/de';
const german = {
locale: 'de',
messages: { ...coreDe, ...tableDe },
searchAliases: { ...coreAliasesDe, ...tableAliasesDe },
} satisfies I18nOptions;
editor.i18n.set(german);
Missing messages fall back to English. Runtime language changes update Domternal's UI without translating or changing the document. Domternal does not add a language selector; your application chooses when to pass a new configuration. See the localization guide for wrapper examples, partial catalogs, formatting, and custom translation resolvers.
In 1.4.0, message authors can mark additive definitions with optionalTranslation: true.
CompleteMessages keeps existing unmarked messages required while allowing those new
translations to use the built-in fallback. Use Required to
check a translation for every current message.
Version compatibility
Upgrade installed @domternal/* packages together to 1.7.0. Wrappers require
core >=1.7.0 <2.0.0, and extensions require both core and pm as peers in that
range. Core 1.7.0 requires pm >=1.7.0 <2.0.0 at runtime.
Version 1.7.0 adds annotateDocumentNotes
for application-owned static previews. Pass a full-document snapshot to keep
footnote and endnote labels consistent across rendered fragments, and optionally
pass the editor's presentation service for translated headings and accessible
reference labels. The helper changes presentation only; existing JSON and schema
configuration remain unchanged. An explicit snapshot keeps missing or duplicate
identities unresolved instead of inventing local numbers.
Version 1.6.0 introduced opt-in image transforms,
document notes and
table layout settings,
plus compound and local PNG list labels.
Enable the corresponding schema options on every collaborative writer before storing
that content. The original ListLabel helpers keep their counter/text contract;
use the additive ExtendedListLabel APIs for the new label kinds.
That release also introduced createSchema and optional print resource preparation, with matching
Core, Table and theme support for eligible multirow print headers. DOCX/PDF
adapters for supported models live in the separate Pro Import and Pro Export
packages; this Free release does not publish those adapters.
Version 1.5.1 fixed Paste Cleanup image byte accounting and configuration guidance.
Version 1.5.0 adds optional ListLabels, bounded Word stylesheet typography,
literal block backgrounds and a formatting choice for each paste. Use matching
Core and theme packages for block fills and image upload feedback. ListLabels
is not part of StarterKit: enable the same extension on all collaborative writers
before storing custom labels. Existing schemas remain unchanged when it is omitted.
The opt-in text contrast and optional translation definitions introduced in 1.4.0 remain available; the default contrast policy preserves authored colors. PasteCleanup, explicit list markers, content diagnostics and coordinated clipboard handling were introduced in 1.3.0; Core 1.2.0 lacks those APIs.
The migration requirements for content validation and transaction callbacks
introduced in 1.3.0 still apply. Loading JSON normalizes unsupported heading levels,
list markers and table spans, and removes links the configured URL policy refuses
while keeping their text.
Stored JSON, strict loaders and mixed-version collaborative documents may need
migration. Review the 1.3.0 breaking changes and the
Core migration guidance before upgrading; use
onContentDiagnostic to observe repairs and normalizeContentAttributes for
an explicit migration of a live document.
Packages
| Package | Description |
| -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @domternal/core | Editor engine with built-in nodes and marks, optional document notes, list-label and headless schema helpers, toolbar controller and icons |
| @domternal/theme | Light and dark themes with 150+ CSS custom properties |
| @domternal/angular | Angular wrapper: 6 components (editor, toolbar, bubble menu, floating menu, emoji picker, notion color picker) |
| @domternal/react | React 18+ wrapper: composable component, toolbar, bubble menu, floating menu, emoji picker, notion color picker, node views |
| @domternal/vue | Vue 3.3+ wrapper: composable component, composables, toolbar, bubble menu, floating menu, emoji picker, notion color picker, node views |
| @domternal/vanilla | Framework-free class-based wrapper for Astro, Svelte, Solid, plain HTML, and Web Components |
| @domternal/pm | ProseMirror re-exports (state, view, model, transform, commands, keymap, history, tables, and more) |
| @domternal/extension-block-controls | Notion-style block UX: block handle, context menu, drag-to-reorder, keyboard reorder, slash command, floating menu, smart paste (formerly @domternal/extension-block-menu) |
| @domternal/extension-toc | Notion-style Table of Contents: floating outline, inline /toc block, scrollToHeading command |
| @domternal/extension-table | Tables with merge, split, resize, cell styling, row/column controls and opt-in row/cell layout settings |
| @domternal/extension-math | LaTeX math: inline and block equations with a pluggable renderer (KaTeX) |
| @domternal/extension-markdown | GitHub-flavored Markdown import and export: markdown paste, insertMarkdown/setMarkdownContent, headless parser and serializer with fidelity warnings |
| @domternal/extension-paste-cleanup | Opt-in Office HTML paste normalization, preserve/adapt formatting, and bounded diagnostics through the extension or standalone /html entry |
| @domternal/extension-image | Images with paste/drop upload, URL input, XSS protection, bubble menu and opt-in crop, rotate and mirror geometry |
| @domternal/extension-emoji | Emoji picker panel and :shortcode: autocomplete |
| @domternal/extension-mention | @mention autocomplete with multi-trigger and async support |
| @domternal/extension-details | Collapsible details/accordion blocks |
| @domternal/extension-code-block-lowlight | Syntax-highlighted code blocks powered by lowlight |
The table above lists all 18 current MIT packages in this workspace, including @domternal/extension-paste-cleanup, introduced in 1.3.0. npm search may still show @domternal/extension-block-menu as a historical, deprecated 0.x entry. It is not an additional current package. Briefly published 1.0.x builds were withdrawn from the registry; import @domternal/extension-block-controls directly.
PasteCleanup normalizes pasted HTML, rebuilds lists from explicit Office list metadata, and keeps a bounded set of inherited inline formatting. Reviewed native captures qualify Word 16.113.3 for Mac copied in Safari 26.5.2, Chrome 154 and Firefox 155, and Google Docs web copied in Chrome 153 on macOS. Those captures were replayed through synthetic paste events; the current English regression fixtures are authored derivatives, not new native captures. Google Docs in Safari and Firefox, Word for Windows, Word on the web, LibreOffice and Collabora remain unqualified. See the support matrix for exact limits. DOCX file import is available separately in Domternal Pro.
See Packages & Bundle Size for a full breakdown of what each package includes and how tree-shaking works.
Domternal Pro
Domternal Pro adds the collaborative layer on top of these MIT packages, published as @domternal-pro/* on the public npm registry under a commercial license: real-time collaboration with live carets and presence, comments, version history, an AI assistant, columns, Word document import, Word and PDF export, and a self-hosted collaboration server.
- Installation and licensing - install from npm, evaluate without a key, activate with a Commercial Key
- Pricing - plans and what each one includes
- Support and reporting issues - Pro defects are tracked in this repository through the Pro bug report template
Documentation
Full documentation, live playgrounds, and API reference at domternal.dev.
- Getting Started - install and create your first editor
- Introduction - core concepts, architecture, and design decisions
- Packages & Bundle Size - what each package includes and bundle size breakdown
Contributing
See CONTRIBUTING.md for commit conventions, PR guidelines, and development setup.
pnpm install # Install dependencies
pnpm build # Build all packages
pnpm test # Run tests
pnpm lint # Run linter
pnpm typecheck # Type check
Requires Node.js >= 22 and pnpm >= 10.
License
The source code is available under the MIT License. Use of the Domternal name and logo is governed by the Trademark Usage Policy.