Alpha — API in active development. Breaking changes expected.
Nabu is a modular, local-first block editor engine for Svelte 5. It combines the intuitive cross-block selection of document editors (Google Docs, ProseMirror) with the structural flexibility of block editors (Notion), built on a Single ContentEditable architecture and a CRDT core (Loro).
npm install @aionbuilders/nabu
# or
bun add @aionbuilders/nabuNabu depends on loro-crdt, which ships as a WebAssembly module. You must configure your Vite bundler to handle it.
1. Install the required Vite plugins:
npm install -D vite-plugin-wasm vite-plugin-top-level-await2. Add them to your vite.config.js:
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite'; // or svelte()
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default defineConfig({
plugins: [
sveltekit(),
wasm(),
topLevelAwait()
]
});Without this setup, Nabu will fail to load.
With a preset (recommended):
<script>
import { NabuEditor, createFullEditor } from '@aionbuilders/nabu';
const engine = createFullEditor();
</script>
<NabuEditor {engine} />Manual setup:
<script>
import {
Nabu,
NabuEditor,
ParagraphExtension,
HeadingExtension,
ListExtension,
ListItemExtension,
DialogueExtension,
RichTextExtension
} from '@aionbuilders/nabu';
const engine = new Nabu({
extensions: [
ParagraphExtension,
HeadingExtension,
ListExtension,
ListItemExtension,
DialogueExtension,
RichTextExtension
]
});
</script>
<NabuEditor {engine} />Nabu ships with ready-to-use presets for common use cases.
Composable — spread and extend them with your own extensions:
import { FullPreset, createEditor } from '@aionbuilders/nabu';
const engine = createEditor({
preset: FullPreset,
extensions: [MyCustomExtension]
});| Preset | Includes |
|---|---|
MinimalPreset |
Paragraph only |
TextPreset |
Paragraph + rich text marks |
DocumentPreset |
Paragraph + Headings + rich text |
FullPreset |
All built-in blocks + rich text (including Dialogue) |
One-liner shortcuts for the most common cases:
import {
createMinimalEditor, // paragraph only — textarea replacement
createTextEditor, // paragraph + bold/italic/...
createDocumentEditor, // paragraph + headings + rich text
createFullEditor, // everything
createEditor // generic — takes any preset
} from '@aionbuilders/nabu';
const engine = createFullEditor();
// Add custom extensions on top of a preset
const engine = createTextEditor({ extensions: [MyExtension] });All block types and behaviors are provided as extensions. Pass them to the Nabu constructor:
| Extension | Provides | Keyboard shortcuts |
|---|---|---|
ParagraphExtension |
paragraph block |
— |
HeadingExtension |
heading block (h1–h6) |
# → h1, ## → h2, ... |
ListExtension |
list container (bullet/ordered) |
— |
ListItemExtension |
list-item block |
Tab indent, Shift+Tab unindent |
DialogueExtension |
dialogue block |
-- → dialogue, Backspace at start → paragraph |
RichTextExtension |
Inline marks (bold, italic...) | Ctrl+B/I/U/E, Ctrl+Shift+X |
When RichTextExtension or any text-bearing block is active, Nabu automatically applies typographic substitutions as you type:
| Input | Output | Description |
|---|---|---|
-- + space |
— |
Em-dash (French dialogue convention) |
// Insert blocks
engine.insert('paragraph', { text: 'Hello' });
engine.insert('heading', { level: 1, text: 'Title' });
engine.insert('list', { listType: 'bullet' });
engine.insert('dialogue', { text: 'Tu viens ce soir ?' });
// Undo / Redo
engine.undo();
engine.redo();
// Serialize
const markdown = engine.serialize('markdown');
// dialogue blocks serialize as: — Tu viens ce soir ?
const json = engine.serialize('json');The entire editor lives in a single contenteditable root. This enables seamless cross-block selection, native copy-paste, and consistent browser performance.
All state is stored in a Loro document — a WASM-based CRDT. This provides:
- Local-first: offline editing out of the box
- Collaboration-ready: conflict-free concurrent edits
- Rich text: native delta/mark support via
LoroText
Every block type is an extension. You can build custom block types by extending the Block or MegaBlock base classes and registering them via the extension() helper.
Nabu uses $state and $derived throughout for fine-grained reactivity — only modified blocks re-render.
This is an alpha release. The core editing experience is functional, but some features are still in development:
- Persistence (IndexedDB)
- Floating toolbar UI
- Slash command menu
- Link support
Contributions and feedback welcome.