Introduction
On this page
An embeddable rich text editor written in TypeScript. It has its own document engine, a default interface, a headless API and adapters for the common frameworks. Everything runs in your app with no licence server, load metering or required hosted service.
Quick start
import { createRichTextEditor } from '@better-rich-text/core';
const editor = createRichTextEditor('#editor', {
content: '<p>Hello</p>',
placeholder: 'Start writing…',
onChange: (instance) => save(instance.getHTML()),
});createRichTextEditor accepts a selector, an element or a <textarea> (which it keeps in sync for form posts). Use mode: 'inline' to edit an element in place, or mode: 'bare' for the editing surface without chrome.
Presets
The default is the standard preset. @better-rich-text/presets adds ready-made ones for long documents, collaboration, email and demos, each imported from its own subpath so you only bundle the one you use:
import { documentPreset } from '@better-rich-text/presets/document';
createRichTextEditor('#editor', { preset: documentPreset });See docs/presets.md for what each preset includes, its size, and how to trim or extend it.
Packages
| Package | Purpose |
|---|---|
model |
Immutable document tree, schema, runtime validation, Unicode helpers, JSON envelope |
engine |
Transactions, typed commands, plugins, history, filters and actions, statistics, tool definitions |
dom |
Browser view: rendering, selection, input, composition, clipboard, decorations |
ui |
Toolbar, menus, dialogs, status bar and opt-in features |
skins |
Visual skins (studio default; others load on demand) |
core |
Presets and createRichTextEditor |
presets |
Document, collaboration, email and full presets (see docs/presets.md) |
format-html, format-markdown |
Import and export |
plugin-* |
Marks, blocks, links, lists, code blocks, text style, images, image editing, tables, search, paste cleanup, typography and autocorrect, format painter, styles, page breaks, templates, files, limits, saving, proofing and more |
react, vue, angular, svelte, aurelia, solid, web-component, legacy |
Framework adapters (see docs/frameworks.md) |
Headless use
The engine has no DOM dependency, so the same commands run on a server or in a worker:
import { createEditor } from '@better-rich-text/engine';
import { marksPlugin } from '@better-rich-text/plugin-marks';
const editor = createEditor({ plugins: [marksPlugin] });
editor.execute('text.insert', { text: 'Hello' });
editor.execute('selection.selectAll');
editor.execute('format.toggleBold');Command names and inputs are typed from the installed plugins, and the same schemas validate input at runtime.
Extending
Plugins declare nodes, marks, commands, key bindings, input rules and normalisers. Beyond events, every layer exposes filters and actions with priorities and namespaces, like WordPress hooks:
editor.hooks.addFilter('content.insertText', (text) => text.replace(/--/g, '\u2013'), { namespace: 'my-plugin' });
editor.hooks.addAction('transaction.committed', (event) => audit(event), { namespace: 'my-plugin' });See docs/adr/0003-filters-and-actions.md.
Bundle size
Features are separate packages and the interface features are opt-in, so unused features are not bundled. pnpm size reports gzip sizes against the specification budgets:
| Build | Budget | Current |
|---|---|---|
| Engine only | 50 KiB | 35 KiB |
| Minimal editor | 140 KiB | 80 KiB |
| Standard editor | 300 KiB | 161 KiB |
| Document preset | 450 KiB | 391 KiB |
| Collaboration preset | 400 KiB | 228 KiB |
| Email preset | 300 KiB | 198 KiB |
| Full preset | 600 KiB | 455 KiB |