Skip to content

Packages & Bundle Size

The core package is the foundation of Domternal. It contains the editor engine, 49 built-in nodes/marks/extensions, toolbar controller, bubble menu, floating menu controller, and 49 icons, all in a single package.

  • 13 Nodes: Document, Text, Paragraph, Heading, Blockquote, CodeBlock, BulletList, OrderedList, ListItem, TaskList, TaskItem, HorizontalRule, HardBreak
  • 9 Marks: Bold, Italic, Underline, Strike, Code, Link, TextStyle, Subscript, Superscript
  • 27 Extensions: StarterKit, BaseKeymap, History, Dropcursor, Gapcursor, TrailingNode, ListKeymap, ListIndent, LinkPopover, BubbleMenu, Placeholder, CharacterCount, Typography, TextAlign, TextColor, FontFamily, FontSize, LineHeight, Highlight, Focus, UniqueID, Selection, SelectionDecoration, InvisibleChars, ClearFormatting, NotionColorPicker, BlockColor
  • Toolbar: ToolbarController, 49 Phosphor icons, defaultIcons export
  • Floating Menu: FloatingMenuController headless state machine in core (items API, addFloatingMenuItems extension hook, roving tabindex, Alt-F10 / Mod-/ keymap). The FloatingMenu extension that wires it into the editor ships from @domternal/extension-block-menu
  • Utilities: positionFloating, positionFloatingOnce (powered by @floating-ui/dom), writeToClipboard, list helpers (getListItemCursorContext, liftEmptyChildrenZoneParagraph, etc.), groupFloatingMenuItems, defaultBubbleContexts

Bundle size: ~44 KB (own code) + ~73 KB (ProseMirror)

Section titled “Bundle size: ~44 KB (own code) + ~73 KB (ProseMirror)”

Domternal’s own code is ~44 KB gzipped. The rest is ProseMirror, the document editing engine that every ProseMirror-based editor ships with.

Bundlephobia reports the total as ~117 KB gzipped because @domternal/core includes ProseMirror as a dependency. When you install core, you get everything you need to run the editor, no additional packages required.

Other editors split this differently. For example, @tiptap/core shows ~28 KB gzipped on bundlephobia, but that’s only the engine without any extensions. You still need @tiptap/starter-kit, individual extension packages, and @tiptap/pm (ProseMirror). Similarly, ngx-tiptap is only a few KB, but it requires @tiptap/core and all its dependencies on top. Once you add everything needed for a working editor, the total is comparable or larger.

Domternal bundles it all in one package, so the bundlephobia number reflects everything. Everything is tree-shakeable, so you import only what you need and your bundler eliminates the rest. Here’s the full breakdown:

ComponentGzippedWhat it does
Domternal core (self)~44 KBEditor engine, 27 extensions, toolbar controller, bubble menu, floating menu (items API), 49 icons
prosemirror-view~28 KBDOM rendering, cursor handling, input events, decorations
prosemirror-model~13 KBDocument schema, node/mark types, content parsing and serialization
prosemirror-transform~9 KBDocument transformations, steps, mapping, operational transform
linkifyjs~6 KBURL detection for automatic link creation
prosemirror-state~3 KBEditor state management, transactions, plugins
prosemirror-commands~3 KBBuilt-in editing commands (join, lift, split, select)
Other ProseMirror packages~11 KBHistory, keymap, input rules, dropcursor, gapcursor, schema-list, tables

Bundle size composition of @domternal/core showing Domternal's own code on top of ProseMirror dependencies

The ~117 KB is the maximum possible size. In practice, your bundle will be smaller. Domternal is marked as sideEffects: false, which means your bundler (Vite, webpack, esbuild) automatically removes any code you don’t import. If you use Bold, Italic, and Paragraph but not CodeBlock, TaskList, or Typography, those extensions are eliminated from your final build. You only pay for what you use.


Domternal ships as 15 npm packages under the @domternal scope. Here’s what you need depending on your setup.

The only required package. @domternal/pm (ProseMirror) is included as a dependency, so you don’t need to install it separately. This is enough for a fully functional headless editor with vanilla JavaScript or TypeScript. You provide your own CSS.

PackageDescription
@domternal/coreEditor engine, 49 built-in nodes/marks/extensions, toolbar controller, bubble menu, floating menu controller, 49 icons
@domternal/pmProseMirror re-exports (model, view, state, transform, commands, history, keymap, inputrules, dropcursor, gapcursor, schema-list, tables). Automatically installed with core

Optional for vanilla setups, required for Angular and React components. Provides ready-made editor styles so you don’t have to write them from scratch.

PackageDescription
@domternal/themeLight and dark editor themes, toolbar and bubble menu styles, 70+ CSS custom properties for customization

Requires core and theme. Provides ready-made Angular components that handle toolbar rendering, bubble menus, and state management automatically.

PackageDescription
@domternal/angular5 Angular components: editor, toolbar, bubble menu, floating menu (in progress), emoji picker. Signals, OnPush, standalone

Requires core and theme. Provides React hooks and components that handle toolbar rendering, bubble menus, and state management automatically.

PackageDescription
@domternal/reactReact 18+ components and hooks: composable editor, toolbar, bubble menu, floating menu, emoji picker, custom node views

Requires core and theme. Provides Vue 3 composables and components that handle toolbar rendering, bubble menus, and state management automatically.

PackageDescription
@domternal/vueVue 3.3+ composables and components: editor, toolbar, bubble menu, floating menu, emoji picker, notion color picker, custom node views with appContext forwarding

Requires core and theme. Provides framework-free DOM classes for Astro, Svelte, Solid, plain HTML, and Web Components. Class-based API with new DomternalX(host, opts) + .destroy() + setters + EventTarget events. ESM-only with per-component subpath exports for tree-shaking.

PackageDescription
@domternal/vanilla6 classes: DomternalEditor, DomternalToolbar, DomternalBubbleMenu, DomternalFloatingMenu, DomternalEmojiPicker, DomternalNotionColorPicker. SSR-safe (constructor throws server-side; Astro client:only friendly)

Additional features available as separate packages. Each requires @domternal/core.

PackageWhat it adds
@domternal/extension-table4 nodes (Table, TableRow, TableCell, TableHeader), 18 commands (merge, split, resize, styling), cell toolbar
@domternal/extension-imageImage insertion via paste, drag-and-drop, and URL, with image bubble menu
@domternal/extension-emojiEmoji node, picker component, :shortcode: autocomplete suggestions
@domternal/extension-mentionMention node with autocomplete suggestion renderer
@domternal/extension-detailsCollapsible content blocks: 3 nodes (Details, DetailsSummary, DetailsContent)
@domternal/extension-code-block-lowlightSyntax-highlighted code blocks via lowlight integration
@domternal/extension-block-menuNotion-style block UX: BlockHandle (drag-to-reorder + + button), BlockContextMenu (Delete / Duplicate / Turn into / Colors / Copy link), SlashCommand (/ insert popup), SmartPaste, KeyboardReorder (Mod-Shift-Up/Down)
@domternal/extension-tocNotion-style Table of Contents: TableOfContents (heading observer + scrollToHeading command), FloatingTocOutline (sticky outline with hover-expanded card), TableOfContentsBlock (inline /toc atom node)

Tree-shaking is a build optimization where your bundler (Vite, webpack, esbuild) analyzes which code you actually import and removes everything else from the final bundle. If a function or class is never imported, it doesn’t end up in your production build.

Domternal is built for tree-shaking. Every package is marked as sideEffects: false, which tells your bundler that any unused export can be safely removed. Each extension is a standalone class with no side effects, so importing Bold doesn’t pull in CodeBlock, Table, or anything else you didn’t ask for.

When you write this:

import { Editor, Document, Text, Paragraph, Bold, Italic } from '@domternal/core';

Your bundler includes only Editor, Document, Text, Paragraph, Bold, and Italic. The other 43 extensions (headings, lists, code blocks, task lists, text alignment, font size, etc.), the toolbar controller, the 49 icons, and every other unused export are eliminated from your bundle. They exist in the package but never reach the browser.

When you write this:

import { Editor, StarterKit } from '@domternal/core';

StarterKit bundles ~27 extensions together (paragraphs, headings, lists, blockquotes, code blocks, history, and more), so all of those are included. Extensions not part of StarterKit (like TextColor, FontSize, Typography, Highlight) are still excluded.

The ~44 KB core engine size is the maximum when you import everything. Your actual bundle depends on what you use:

SetupDomternal code included
Editor + Document + Text + Paragraph + a few marksWell under 44 KB
StarterKit (~27 extensions)Roughly half of the 44 KB. Does not include ToolbarController, icons, BubbleMenu, FloatingMenu, TextColor, FontSize, FontFamily, LineHeight, Highlight, Typography, NotionColorPicker, BlockColor, ListIndent, and other optional extensions
StarterKit + all optional extensions + toolbar + iconsFull 44 KB

ProseMirror is also tree-shakeable. The core ProseMirror packages (model, view, state, transform) are always required (~60 KB), but packages like prosemirror-history, prosemirror-tables, and prosemirror-gapcursor are only included if you use the corresponding extensions.

Framework wrappers (Angular, React, Vue, Vanilla)

Section titled “Framework wrappers (Angular, React, Vue, Vanilla)”

The framework wrapper packages are also tree-shakeable. If you use the compound Domternal component (Angular/React/Vue), all subcomponents (Toolbar, BubbleMenu, FloatingMenu, EmojiPicker, NotionColorPicker) are included because they are part of the compound. If you only need some of them, import the standalone components instead:

// Compound - includes all subcomponents
import { Domternal } from '@domternal/vue';
// Standalone - only what you import ends up in your bundle
import { useEditor, DomternalToolbar, EditorContent } from '@domternal/vue';

The same applies to React (Domternal compound vs standalone components). Angular uses standalone components only, so each component is individually tree-shakeable by default.

The Vanilla wrapper (@domternal/vanilla) takes this further with per-component subpath exports - import only what you need:

// Barrel import - tree-shaking removes unused
import { DomternalEditor, DomternalToolbar } from '@domternal/vanilla';
// Subpath imports - bundler only loads each file you reference
import { DomternalEditor } from '@domternal/vanilla/editor';
import { DomternalToolbar } from '@domternal/vanilla/toolbar';
import { DomternalBubbleMenu } from '@domternal/vanilla/bubble-menu';

Why ProseMirror is a dependency (not bundled)

Section titled “Why ProseMirror is a dependency (not bundled)”

Domternal keeps ProseMirror as a regular dependency via @domternal/pm rather than bundling it into the dist. This ensures that if you use multiple @domternal packages (core + extension-table + extension-image), ProseMirror is shared and deduplicated by your package manager, not duplicated in each package.

Some editors bundle ProseMirror into their dist output, which can result in a smaller bundlephobia number but can lead to duplicate ProseMirror code when using multiple packages.