A headless, AST-driven Markdown editor engine β built for makers who need real Markdown, not another WYSIWYG.
Powered by CodeMirror 6 + the unified ecosystem. Framework-agnostic core Β· Official React & Vue bindings Β· MIT licensed.
δΈζζζ‘£ Β· Quick Start Β· Why Nexus? Β· Roadmap Β· Contributing
We ship in priority tiers β P0 is what we're working on right now.
| Tier | Theme | Highlights |
|---|---|---|
| P0 β Now | Core API completeness | getSelectedText(), slash command sorting, <Editor /> onReady, TS strict coverage |
| P1 β Next | Power-user features | Multi-cursor, regex search, undo/redo grouping, widget API standardization, Electron packaging |
| P2 β Mid-term | UX & ecosystem | Advanced toolbar (emoji / table / color), fuzzy search, sync-scroll preview, web-component wrapper |
| P3 β Long-term | Collaboration | Realtime CRDT collab, shared comments / @mention, plugin hot-reload |
π Full roadmap with package ownership, status, and OpenSpec linkage: docs/ROADMAP.md
Markdown editing on the web is a crowded space β yet every existing option forced us to compromise on something fundamental. We built Nexus because we needed an editor that treats Markdown text as the source of truth, stays out of the way of your UI, and is honest about extension points.
| Nexus | Tiptap | Lexical | Milkdown | MDXEditor | @uiw/react-md-editor | |
|---|---|---|---|---|---|---|
| Foundation | CodeMirror 6 + unified | ProseMirror | Built from scratch (Meta) | ProseMirror + Remark | Lexical | CodeMirror + Marked |
| Source of truth | Markdown text | JSON tree | JSON tree | ProseMirror doc | Lexical state | Markdown text |
| Round-trip safe | β Lossless | β | ||||
| UI model | Headless | Headless | Headless-ish | WYSIWYG (built-in UI) | WYSIWYG | Split-pane |
| Live preview | Obsidian-style inline | None (BYO) | None (BYO) | Full WYSIWYG | Full WYSIWYG | Side-by-side pane |
| Framework support | React Β· Vue Β· Vanilla | React Β· Vue Β· Svelte Β· Vanilla | React-first | React Β· Vue Β· Vanilla | React only | React only |
| Plugin tiers | 3 (shortcut / AST / CM6) | 1 (Tiptap extension) | 1 (node + transform) | 1 (Milkdown plugin) | 1 (Lexical plugin) | None |
| Bundle (core, approx.) | Small (CM6 ~110KB) | Medium (PM + ext.) | ~22KB core | ~125KB / ~40KB gz | ~851KB gz | Medium |
| Local-first / file IO hooks | β First-class | β | β | β | β | β |
| License | MIT | MIT (+ paid cloud) | MIT | MIT | MIT | MIT |
Numbers are taken from public docs and recent third-party reviews. Bundle sizes vary with plugins enabled β treat the column as a rough order-of-magnitude.
- Tiptap / Lexical / Milkdown / MDXEditor all keep an internal JSON document model. Markdown is an import/export concern β round-tripping a
.mdfile through them can quietly drift (lost soft-breaks, reordered attributes, normalized tables). If your product is the Markdown file (notes app, static-site authoring, LLM writing tools), this matters. - @uiw/react-md-editor keeps Markdown as the document, but offers a classic split-pane preview β no inline syntax reveal, no widget API, React only.
- Obsidian has the UX we love, but it's closed source. You can't embed its engine in your own product.
- Nexus keeps Markdown as the document and gives you Obsidian-style live preview, a widget API, three plugin altitudes, and framework-agnostic bindings β without the WYSIWYG lock-in.
If you're building a note-taking app, a docs CMS, a static-site authoring tool, a Markdown-native PKM, or an LLM-powered writing assistant, Nexus is meant to be the engine you don't have to fight.
# pnpm (recommended)
pnpm add @floatboat/nexus-core @floatboat/nexus-preset-gfm
# or npm / yarn
npm install @floatboat/nexus-core @floatboat/nexus-preset-gfmReact β the fast path
import { Editor } from "@floatboat/nexus-react";
import { createGfmPreset } from "@floatboat/nexus-preset-gfm";
export default function App() {
return (
<Editor
initialValue="# Hello, Nexus π"
plugins={[createGfmPreset()]}
livePreview
onChange={(doc, ast) => console.log(doc)}
/>
);
}π‘ New to CodeMirror? You don't need to know it.
<Editor />handles the lifecycle β just drop it in.
Vue 3
<script setup>
import { Editor } from "@floatboat/nexus-vue";
import { createGfmPreset } from "@floatboat/nexus-preset-gfm";
</script>
<template>
<Editor
initial-value="# Hello"
:plugins="[createGfmPreset()]"
:live-preview="true"
@change="(doc) => console.log(doc)"
/>
</template>Vanilla / Plain DOM
import { createEditor } from "@floatboat/nexus-core";
import { createGfmPreset } from "@floatboat/nexus-preset-gfm";
import { createHistoryPlugin } from "@floatboat/nexus-plugin-history";
const editor = createEditor({
container: document.getElementById("editor")!,
initialValue: "# Hello\n\nStart typing...",
plugins: [createGfmPreset(), createHistoryPlugin()],
livePreview: true,
onChange(doc, ast) {
console.log("Markdown:", doc);
console.log("AST:", ast);
},
});π£ Things we wish someone had told us when we started
- You don't need to know CodeMirror. The React / Vue wrapper handles the lifecycle. You only meet CodeMirror if you write a low-level plugin.
- Headless means no theme. We ship logic, not looks. Plan a few hours for styling β the Electron demo is a copy-pasteable starting point.
- Live preview is opt-in. If you just want a raw Markdown editor, leave it off and you get a clean text-editing experience.
- The AST is
mdastβ the same tree used byremarkand the unified ecosystem. If you've never seen it, the mdast spec is the one-page cheat sheet you actually need.- Don't auto-save on every keystroke.
onChangefires constantly β debounce before writing to disk or hitting the network.- Blank screen? Nine times out of ten, the container has no height. Give it one and the editor appears.
- Lost? Open an issue with the
questionlabel β we'd rather answer the same question 20 times than have you give up.
git clone https://github.com/floatboatai/Nexus-Editor.git
cd Nexus-Editor
pnpm install
pnpm dev:electron-demoA real Electron app with file IO, live preview, and every plugin enabled β the fastest way to see what's possible.
Full package list (11 packages) β click to expand
| Package | Description |
|---|---|
@floatboat/nexus-core |
Editor engine β CM6 state, AST pipeline, live preview, events, widget API |
@floatboat/nexus-react |
React binding β useEditor hook and <Editor /> component |
@floatboat/nexus-vue |
Vue 3 binding β useEditor composable |
@floatboat/nexus-preset-gfm |
GitHub Flavored Markdown preset (tables, strikethrough, task lists) |
@floatboat/nexus-plugin-history |
Undo/redo with Ctrl+Z / Ctrl+Shift+Z |
@floatboat/nexus-plugin-search |
Search and replace helpers |
@floatboat/nexus-plugin-slash |
Slash command detection, ranking, and a vanilla-DOM floating menu UI |
@floatboat/nexus-plugin-toolbar |
Toolbar primitives for formatting commands |
@floatboat/nexus-plugin-math |
Inline / block math rendering (KaTeX) |
@floatboat/nexus-plugin-vim |
Vim keybindings powered by @replit/codemirror-vim |
@floatboat/nexus-plugin-wordcount |
Markdown-aware word / character / CJK / reading-time stats + ARIA-live status bar |
- Headless β no built-in UI. Render with any framework or plain DOM.
- AST-Driven β real-time Markdown β mdast parsing with every keystroke.
- Live Preview β inline rendering that reveals raw syntax on cursor focus (Obsidian-style).
- Plugin System β three tiers: shortcuts & slash commands, remark plugins & widgets, raw CM6 extensions.
- Event System β subscribe to
change,focus,blur,selectionChange,slashMenuChange. - Widget API β render custom components for any AST node type (code blocks, tables, diagrams).
- Local-First β built for Electron/Tauri with file IO hooks and debounced parsing.
Editor API β methods and events
createEditor(config) returns an EditorAPI with:
editor.getDocument() // current Markdown string
editor.getAst() // current mdast Root
editor.setDocument(md) // replace entire document
editor.setDocument(md, { silent: true, preserveSelection: true })
editor.setDocument(md, { selection: { anchor: 0 } })
editor.setSelection(pos) // move cursor
editor.focus() / editor.blur()
editor.destroy()
// Event system
editor.on("change", (doc, ast) => { ... })
editor.on("selectionChange", ({ anchor, head }) => { ... })
editor.on("slashMenuChange", ({ isOpen, query, commands, coords }) => { ... })
editor.off("change", handler)
// Coordinates (for floating UI)
editor.getCoordsAtPos(pos) // { left, right, top, bottom } | nullPlugin authoring β three tiers, one shape
const myPlugin: NexusPlugin = {
name: "my-plugin",
// Tier 1: Shortcuts & slash commands
shortcuts: [{ key: "Mod-b", run: (editor) => { /* toggle bold */ return true; } }],
slashCommands: [{ id: "heading", title: "Heading", keywords: ["h1"] }],
// Tier 2: AST & widgets
remarkPlugins: [remarkMath],
widgets: [{
nodeType: "code",
match: (node) => node.lang === "mermaid",
render: (node, source) => renderMermaidChart(source),
destroy: (el) => el.remove(),
}],
// Tier 3: Raw CM6
cmExtensions: [myCodeMirrorExtension],
};Build, test, and run the demo
pnpm install
pnpm build # build all packages
pnpm test # run all tests
# Electron demo
pnpm dev:electron-demoWe'd love your help β whether it's a typo fix, a new plugin, or a deep core change. Here's the 5-minute version:
- Fork this repo β click the Fork button at the top of the page.
- Clone your fork locally:
git clone https://github.com/<your-username>/Nexus-Editor.git cd Nexus-Editor pnpm install
- Create a branch following our naming convention (see
CONTRIBUTING.md):git checkout -b feat/<scope>/<short-description>
- Make your change β write tests, run
pnpm test, runpnpm build. - Commit using Conventional Commits:
git commit -m "feat(core): add getSelectedText() API" - Push to your fork and open a Pull Request against
main.
- CONTRIBUTING.md β branch naming, Conventional Commits scope whitelist, when to file an OpenSpec proposal, test matrix.
- .github/PULL_REQUEST_TEMPLATE.md β the PR description template.
- openspec/AGENTS.md β required for new capabilities or breaking API changes.
π’ Good first issues are labeled
good first issueβ start there if you're new to the codebase.
MIT Β© floatboat
If Nexus-Editor saved you from writing yet another Markdown editor from scratch, the kindest thing you can do is hit the β button β it helps other devs find the project, and it genuinely makes our day.
- π¦ Tweet about it β tag us, we'll retweet
- π Blog about your integration β open a PR to list it in
SHOWCASE.md - π¬ Share in your team's Slack / Discord β that's how 90% of devs discover tools
- π Open an issue if something feels off β even "this is confusing" is valuable feedback
Built with β€οΈ for makers who still believe Markdown is the right format.