What you write
is what you get.
Rune is a headless, config-driven WYSIWYG editor built from scratch — no ProseMirror, no Slate, no bundler. Wire up every block, mark, and plugin from a single file, add real-time collaboration when you need it, then style 100% of it with CSS variables.
npm install @parityfox/rune-editorjavascript: URLs blockedEverything a document needs, nothing it doesn’t.
Fourteen block types, a dozen inline marks, and seven plugins — plus alignment, indent and line-height. Each one is a toggle in your config; turn things off and the toolbar, menus, and shortcuts all follow. Export to clean HTML, plain text, Markdown, or print.
Block types
14 blocksFrom headings and lists to tables, callouts, code, task lists, toggles, columns, and embedded video — every block is insertable from the slash menu.
Marks & formatting
12 marks + 5 controlsBold, italic, links, inline code, super/subscript, plus font size, family, text colour and highlight — with alignment, indent and line-height. All on sane keyboard shortcuts.
Plugins
7 pluginsMarkdown shortcuts, inline markdown, smart typography, find & replace with regex, drag-to-reorder blocks, a format painter, and emoji autocomplete — drop in as plain objects.
Write together, in real time.
A Yjs-based collaboration layer lets many people edit one document at once — over the network or in-process. The core editor stays dependency-free; the collab deps load only when you turn it on.
Live presence
cursors · selections · typingSee every collaborator's caret, selection highlight and typing indicator move in real time, each in their own colour.
Comments
anchored threadsAnchor threaded comments to any selection. They follow the text as the document changes and resolve when you're done.
Tracked suggestions
accept / reject changesSuggestion mode records edits as proposed changes. Reviewers accept or reject them inline — no overwriting each other's work.
Offline-first
IndexedDB persistenceKeep editing offline; changes persist locally and sync automatically when the connection returns. Full block coverage — incl. per-cell tables.
import * as Y from 'yjs'; import { WebSocketProvider } from '@parityfox/rune-editor/collab/provider.js'; import { bindParagraphSpike } from '@parityfox/rune-editor/collab/paragraph-binding.js'; import { bindPresence } from '@parityfox/rune-editor/collab/presence.js'; const doc = new Y.Doc(); const provider = new WebSocketProvider('ws://localhost:1234', 'my-doc', doc); bindParagraphSpike(editor, doc); bindPresence(editor, doc, provider.awareness, { name: 'Alice', color: '#2563eb', });
Config-driven, to the core.
Flip a boolean in rune.config.js and the entire editor reshapes around it — no other file needs touching.
- 01
Toggle any capability
Every block, mark, and plugin is a single key. Off means it’s gone from the bundle’s surface area, the toolbar, and the slash menu.
- 02
Menus stay in sync
Toolbar, bubble menu, slash menu, and keyboard shortcuts all read from the same config. Define order once.
- 03
Extend with a plain object
Add custom blocks and marks by handing Rune an object. No class hierarchy, no schema DSL to learn.
const config = { blocks: { heading: true, // H1–H3 bulletList: true, codeBlock: true, callout: true, taskList: true, table: true, }, marks: { bold: true, italic: true, link: true, code: true, textColor: true, }, plugins: { markdownShortcuts: true, findReplace: true, dragReorder: true, }, }; export default config;
import { createFromConfig } from '@parityfox/rune-editor'; import config from './rune.config.js'; import '@parityfox/rune-editor/styles'; const editor = createFromConfig('#app', config, { content: '<p>Start writing…</p>', onChange(html) { console.log(html); }, }); // chainable command API editor.chain() .toggleBold() .toggleItalic() .run();
Style every pixel with CSS variables.
Rune ships no opinions about how it looks. Colours, radii, and type are all custom properties on :root. Flip data-theme="dark" for instant dark mode — try the toggle up top.
| rune | ProseMirror | Slate | Quill | |
|---|---|---|---|---|
| Zero dependencies | Yes | No | No | No |
| No build step | Yes | No | No | Yes |
| Headless / CSS-var theming | Yes | Partial | Partial | No |
| Config-driven toolbar | Yes | No | No | Partial |
| Works without a framework | Yes | Yes | No | Yes |
| Real-time collaboration built in | Yes | Add-on | No | No |
Drop it into anything.
Vanilla JS, React, Vue, Svelte, or a Web Component — the same engine, five adapters. Pick your stack and copy the snippet.
import { createFromConfig } from '@parityfox/rune-editor'; import config from './rune.config.js'; import '@parityfox/rune-editor/styles'; const editor = createFromConfig('#app', config, { content: '<p>Start writing…</p>', onChange(html) { console.log(html); }, });
import { RuneEditor } from '@parityfox/rune-editor/react'; import config from './rune.config.js'; import '@parityfox/rune-editor/styles'; export default function App() { return ( <RuneEditor extensions={config.extensions} content="<p>Hello</p>" onChange={(html) => console.log(html)} /> ); }
<script setup> import { RuneEditor } from '@parityfox/rune-editor/vue'; import config from './rune.config.js'; import '@parityfox/rune-editor/styles'; </script> <template> <RuneEditor :extensions="config.extensions" content="<p>Hello</p>" @change="(html) => console.log(html)" /> </template>
<script> import { rune } from '@parityfox/rune-editor/svelte'; import config from './rune.config.js'; import '@parityfox/rune-editor/styles'; </script> <div use:rune={{ config, content: '<p>Hello</p>' }}></div>
<!-- one custom element, no build --> <link rel="stylesheet" href="rune.css"> <script type="module" src="rune-editor.js"></script> <rune-editor content="<p>Hello world</p>" placeholder="Start writing…"> </rune-editor>
Build an editor that
feels like yours.
Zero dependencies. One config file. Full control of every block, mark, and pixel.