Regal Text Editor

A rich text editor, built from scratch.

A small, fully-owned, framework-honest rich text editor toolkit for the web. Headless by default, React-ready out of the box, and extensible through a real plugin system — 11 independently-installable @regal-text-editor/* packages, not a wrapper around ProseMirror, Slate, Lexical, TipTap, or CKEditor.

11
independent packages
462
automated tests
~93%
statement coverage
0
editor-engine dependencies

Live, in your browser

Try it right here

This is the actual @regal-text-editor/react + @regal-text-editor/ui package, running with the full plugin set — not a video or a static mockup.

Why this exists

Key differentiators

Every claim below is backed by source you can read — there is no marketing feature that isn’t also a tested code path.

Built from scratch, not a wrapper

No dependency on any existing rich-text-editor engine. The document model, schema, transaction/operation system, selection model, normalization pass, undo/redo history, and HTML/Markdown/JSON/plain-text serialization are all original code.

Headless-first

@regal-text-editor/core has no DOM and no React dependency at all — it can run in Node for server-side document manipulation, validation, or format conversion, with no browser required.

A real plugin architecture

A plugin registers schema nodes/marks, commands, a keymap, HTML/Markdown serializers, and HTML parsers — through the same typed interface every shipped feature, including every built-in formatting option, also goes through. No privileged, non-pluggable core feature set.

CSS-custom-property theming

Every color, radius, and shadow in the default UI is a CSS variable, overridable from any ancestor element — no CSS-in-JS, no build-step theming requirement. Ships with two ready-made alternate presets, Notion and Midnight, as a live demonstration.

RTL-correct by construction

Logical CSS properties and logical alignment values ("start"/"end", never "left"/"right") throughout, so the UI and text alignment behave correctly under dir="rtl" without a separate RTL mode.

Security-conscious by default

Every URL (link href, image src) passes through an allow-list sanitizer (http:, https:, mailto:, tel:, scheme-less relative — everything else, including javascript: and data:, rejected) at every entry point: HTML paste/import and direct command calls alike.

Honest, itemized status

The README carries a category-by-category self-audit — implemented, partial, or not implemented — instead of a blanket feature list. See the Status section below for the current state.

How it’s layered

Five layers, take only what you need

Each package depends only on the layer(s) below it. Use the engine fully headless, pair it with your own DOM adapter, or bring your own toolbar instead of @regal-text-editor/ui.

LAYER 1@regal-text-editor/coreDocument model, schema, selection, transactions, transforms, history, commands, plugin system, HTML/Markdown/JSON serialization. Zero DOM, zero React (HTML parsing is the one exception — it needs a DOMParser-compatible global).
LAYER 2@regal-text-editor/browserDOM adapter: renders the model to HTML, maps DOM Selection to model Point, handles keyboard / beforeinput / IME composition / clipboard / drag-drop. No React dependency.
LAYER 3@regal-text-editor/uiPre-built toolbar UI (Toolbar, BubbleToolbar, LinkButton, ImageButton, FindReplacePanel, dropdowns). Built only on @regal-text-editor/react's public hooks — never reaches into engine internals.
LAYER 4plugin-*Seven feature plugins, each independent and optional.
LAYER 5@regal-text-editor/reactRichTextEditor, useEditor, useEditorSelector, useCommand. The engine (@regal-text-editor/core, @regal-text-editor/browser) has no dependency on this layer.

What ships

11 packages, install only what you use

All packages: version 0.1.0, MIT-licensed, ESM-only, ship .d.ts declarations and sourcemaps, and are marked sideEffects: false so a bundler can tree-shake unused exports.

PackageWhat it providesDepends onPeer depsSize (unminified)
@regal-text-editor/coreDocument model, schema, transactions, selection, commands, history, plugin system, HTML/Markdown/JSON/text serialization, HTML parsing + sanitization58.4 KB
@regal-text-editor/browserDOM rendering, DOM ↔ model selection mapping, keyboard/IME/clipboard/drag-drop handling@regal-text-editor/core22.8 KB
@regal-text-editor/react<RichTextEditor>, useEditor, useEditorSelector, useCommand@regal-text-editor/core, @regal-text-editor/browserreact ≥18, react-dom ≥184.3 KB
@regal-text-editor/uiDefault toolbar UI: Toolbar, BubbleToolbar, LinkButton, ImageButton, FindReplacePanel, dropdowns, icons, theming CSS@regal-text-editor/core, @regal-text-editor/browser, @regal-text-editor/react, @regal-text-editor/plugin-linkreact ≥18, react-dom ≥1819.1 KB JS + 13.0 KB CSS
@regal-text-editor/plugin-basic-marksBold, italic, underline, strikethrough, inline code@regal-text-editor/core4.5 KB
@regal-text-editor/plugin-basic-blocksParagraph, heading (configurable levels), blockquote, horizontal rule, text alignment@regal-text-editor/core9.4 KB
@regal-text-editor/plugin-listsBullet / ordered / task lists, nesting, indent/outdent@regal-text-editor/core9.7 KB
@regal-text-editor/plugin-imageBlock-level image node, insertImage@regal-text-editor/core2.5 KB
@regal-text-editor/plugin-code-blockPreformatted code block, Enter-inserts-newline behavior@regal-text-editor/core2.5 KB
@regal-text-editor/plugin-historyUndo/redo commands and keyboard shortcuts@regal-text-editor/core0.5 KB

Feature plugins

Every plugin, in exact detail

Commands, keyboard shortcuts, and behavior — verified directly against source, not summarized from memory.

@regal-text-editor/plugin-basic-marksInline formatting

Bold, italic, underline, strikethrough, and inline code.

CommandShortcutDetail
toggleBoldMod-B<strong>/<b> on import, **text** in Markdown
toggleItalicMod-I<em>/<i> on import, _text_ in Markdown
toggleUnderlineMod-U<u> on import, no Markdown equivalent (plain text on export)
toggleStrikethroughMod-Shift-X<s>/<strike>/<del> on import, ~~text~~ in Markdown
toggleInlineCodeMod-E<code> on import, `text` in Markdown
  • Mod resolves to ⌘ on macOS and Ctrl elsewhere.
  • Supports "stored marks" — toggling a mark at a collapsed cursor affects the next typed character, matching mainstream editor behavior.
@regal-text-editor/plugin-basic-blocksBlock types & alignment

Paragraphs, headings, blockquotes, horizontal rules, and logical text alignment.

CommandShortcutDetail
setParagraphMod-Alt-0Converts the current block to a paragraph
setHeading1 … setHeading6Mod-Alt-1 … Mod-Alt-6Configurable heading levels (defaults to 1–6)
toggleBlockquoteNo default shortcut. Multi-paragraph blockquotes wrap/unwrap as a unit.
insertHorizontalRuleInserts a horizontal rule block
setTextAlign(align)"start" | "center" | "end" | "justify" — logical values, correct under dir="rtl"
  • HTML import maps physical left/right from pasted content back to logical start/end (assumes LTR-authored source).
@regal-text-editor/plugin-listsBullet, ordered & task lists

Nesting, indent/outdent, and task checkboxes.

CommandShortcutDetail
toggleBulletListBullet list; ordered lists support a custom start number via <ol start="N">
toggleOrderedListOrdered list
toggleTaskListItemAdds a checked attribute, rendered with a real SVG checkmark in the default UI
listEnterEnterSplits the current item, or exits the list from an empty item
listIndent / listOutdentTab / Shift-TabNests the current item one level deeper/shallower
  • Toggling a multi-item list off unwraps every item correctly (regression-tested).
@regal-text-editor/plugin-imageImages

Block-level images with URL-only sourcing.

CommandShortcutDetail
insertImage(src, alt?)Inserts a block-level image — its own line, like a horizontal rule
  • A deliberate scope decision: the document model has no inline-void/node-selection concept yet, which true inline images need.
  • No built-in file upload adapter — src is sanitized the same way link hrefs are.
  • Exports to both HTML (<img src alt width?>) and Markdown (![alt](src)).
@regal-text-editor/plugin-code-blockPreformatted code

A dedicated code block node with correct Enter-key behavior.

CommandShortcutDetail
toggleCodeBlockMod-Alt-CConverts the current block to/from a preformatted node; no marks allowed inside it
  • Enter inserts a literal line break instead of splitting into a new block, via a generic code:true schema flag the browser adapter reads directly.
  • Records a language attribute per block for a future syntax highlighter — no highlighting renders today.
  • HTML import recognizes <pre> (including <pre><code>) and preserves exact whitespace.
@regal-text-editor/plugin-historyUndo / redo

Snapshot-based history with typing coalescence.

CommandShortcutDetail
undoMod-ZSteps back to the previous snapshot
redoMod-Shift-Z, Mod-YCovers both common conventions
  • Keeps document-reference pairs rather than inverting individual operations — cheap, since the document is a persistent, structurally-shared tree.
@regal-text-editor/core + @regal-text-editor/uiFind & replace

Not a separate plugin package — lives directly in the core search functions, paired with a ready-made panel.

CommandShortcutDetail
findMatches / replaceMatch / replaceAllMatchesCase-insensitive by default; never matches across a block boundary
<FindReplacePanel />Enter/Shift+Enter step forward/backward, Escape closes
  • Only the current match is highlighted via the real editor selection — no separate highlight-every-match decoration layer yet.

@regal-text-editor/ui component reference

The default toolbar UI, built only on @regal-text-editor/react’s public hooks.

ExportPurpose
<Toolbar items={ToolbarItem[]} />Data-driven toolbar; items are button, separator, or dropdown (a styled native <select> for full built-in keyboard/screen-reader support). defaultToolbarItems provides a ready-made complete set.
<BubbleToolbar items={...} />A small formatting toolbar that appears next to the current text selection, built on the useSelectionRect hook.
<LinkButton />Self-contained popover that collects a URL and calls setLink/unsetLink; pre-fills the current link's href when editing.
<ImageButton />Self-contained popover that collects an image URL and alt text and calls insertImage.
<FindReplacePanel />Find/replace UI driven by @regal-text-editor/core's search functions.
Icon / availableIconNamesThe built-in dependency-free SVG icon set (24 icons) used by every other component; swappable per toolbar item.
ToolbarButton, ToolbarSeparator, ToolbarDropdownThe individual pieces <Toolbar> composes, exported for building fully custom toolbar layouts.

Honest, itemized

Feature status

A category-by-category self-audit instead of a blanket feature list — this is the same table that lives in the project’s README.

Implemented

Fully implemented

  • Document model & schema, transactions/operations, selection/position tracking, command system
  • Formatting marks: bold, italic, underline, strikethrough, inline code, link
  • Headings, paragraph, blockquote, horizontal rule, logical text alignment
  • Lists: bullet, ordered, task — nesting, indent/outdent
  • Block-level images, links, code blocks (no syntax highlighting), find & replace
  • HTML import/export, read-only mode, placeholder/empty state
  • The full toolbar UI, including the bubble toolbar
  • CSS-custom-property theming
  • React integration: controlled + uncontrolled, useSyncExternalStore-based subscriptions
Partial

Partially implemented

  • Schema content-matching: a simplified sequential matcher, not a full regular-content-expression engine
  • Undo/redo: snapshot-based, not operation-inversion-based
  • Clipboard: real HTML/text round-trip, no Word/Google-Docs-specific cleanup
  • Drag & drop: content insertion works, no drag-to-reorder-a-block
  • Markdown: export is complete and tested; there is no Markdown import parser yet
  • Accessibility: ARIA roles/labels present, not independently audited against WCAG
  • RTL: logical CSS throughout, no dedicated RTL example or bidi cursor testing
Not started

Not implemented

  • Tables, mentions/bookmarks, a source-code/raw-HTML edit mode
  • Autosave (the editor.subscribe/change hooks exist for a host app to build one)
  • Slash-commands / command palette, table of contents, auto-linking, smart typography
  • Spellcheck integration beyond the browser's native spellcheck attribute
  • Localization of the UI's (currently English-only) labels

Requirements & compatibility

  • Node: ≥18.18 for building/developing the monorepo itself; consumers just need any modern bundler or Node ≥18 for ESM support.
  • React: ≥18 for @regal-text-editor/react and @regal-text-editor/ui (peer dependency, never bundled). @regal-text-editor/core and @regal-text-editor/browser have no React dependency at all.
  • Module format: ESM only — no CommonJS build.
  • Browser: Any evergreen browser with contentEditable and standard Selection/Range APIs. HTML import needs a DOMParser-compatible global — present in every browser and in jsdom, but not in plain Node.
  • License: MIT, on every published package.

Security posture

  • All URLs (link hrefs, image sources) pass through an allow-list sanitizer accepting only http:, https:, mailto:, tel:, and scheme-less relative URLs — javascript:, data:, vbscript:, and any other scheme are rejected outright, both on HTML import and on direct command calls.
  • HTML parsing strips <script>, <style>, <iframe>, <object>, <embed>, <link>, <meta>, <base>, and <form> tags outright, and strips every on* event-handler attribute from every element.
  • HTML export escapes all text content; the only DOM writes the editor ever performs are its own serializer's output applied to a validated in-memory document — never arbitrary external HTML written directly to innerHTML.

Get started

Installation & quick start

Every package is scoped-public (publishConfig.access: "public"), so publishing needs no special npm org setup, and installing needs nothing beyond plain npm install.

1. Install the packages you need

terminal
npm install @regal-text-editor/core @regal-text-editor/browser @regal-text-editor/react @regal-text-editor/ui \
  @regal-text-editor/plugin-basic-blocks @regal-text-editor/plugin-basic-marks @regal-text-editor/plugin-lists @regal-text-editor/plugin-history

2. With React

editor.tsx
import { createBaseSchema } from "@regal-text-editor/core";
import { EditorProvider, RichTextEditor, useEditor } from "@regal-text-editor/react";
import { Toolbar, defaultToolbarItems } from "@regal-text-editor/ui";
import "@regal-text-editor/ui/styles.css";
import { BasicBlocksPlugin } from "@regal-text-editor/plugin-basic-blocks";
import { BasicMarksPlugin } from "@regal-text-editor/plugin-basic-marks";
import { ListsPlugin } from "@regal-text-editor/plugin-lists";
import { HistoryPlugin } from "@regal-text-editor/plugin-history";

function Editor() {
  const editor = useEditor({
    schema: createBaseSchema(),
    plugins: [BasicBlocksPlugin(), BasicMarksPlugin(), ListsPlugin(), HistoryPlugin()]
  });

  return (
    <EditorProvider editor={editor}>
      <Toolbar items={defaultToolbarItems} />
      <RichTextEditor editor={editor} placeholder="Start writing..." onChange={(doc) => console.log(doc)} />
    </EditorProvider>
  );
}

3. Or fully headless — no React, no UI

server.ts
import { Editor, createBaseSchema } from "@regal-text-editor/core";
import { BasicMarksPlugin } from "@regal-text-editor/plugin-basic-marks";

const editor = new Editor({ schema: createBaseSchema(), plugins: [BasicMarksPlugin()] });
editor.commands.execute(editor, "toggleBold");
console.log(editor.getHTML(), editor.getMarkdown(), editor.getJSON());

Questions

Frequently asked questions

Can’t find what you’re looking for? Use search in the header — every answer on this page is indexed.

Is this a wrapper around ProseMirror, Slate, Lexical, TipTap, or CKEditor?

No. The document model, transaction system, selection model, schema engine, undo/redo history, HTML/Markdown serialization, and the browser DOM adapter are all original implementations in this codebase.

Can I use the engine without React?

Yes. @regal-text-editor/core has zero DOM and zero React dependency and can run fully headless, including in Node for server-side document manipulation, validation, or format conversion. Pair it with a custom DOM adapter instead of @regal-text-editor/browser, or use @regal-text-editor/browser directly without React.

How do plugins work?

A plugin registers schema nodes/marks, commands, a keymap, HTML/Markdown serializers, and HTML parsers through one typed interface. Every shipped feature, including bold and headings, is implemented as a plugin through that same interface — there is no privileged, non-pluggable core feature set.

Does it support right-to-left languages?

Yes, by construction. The UI and text alignment use logical CSS properties and logical values ("start"/"end", never "left"/"right"), so it behaves correctly under dir="rtl" without a separate RTL mode. Note this hasn’t yet had a dedicated bidi-cursor test pass — see the Status section.

How do I theme the editor?

Every visual token in @regal-text-editor/ui is a CSS custom property on :root. Import @regal-text-editor/ui/styles.css once, then either use one of the two built-in presets by setting data-rte-theme="notion" or data-rte-theme="midnight" on any ancestor element, or override any subset of the same variables to build a fully custom theme without touching the package’s CSS file.

Can I upload images?

The image plugin is URL-only — there's no built-in file upload adapter. insertImage(src, alt?) accepts whatever URL you supply, typically from your own upload flow, sanitized the same way link hrefs are. Images are also currently block-level only, not inline.

Are tables supported?

Not yet. Tables, mentions/bookmarks, and a raw-HTML edit mode are on the not-implemented list — see the Status section for the full, honest breakdown.

What license is this under?

MIT, on every one of the 11 published packages.