Native Rust WYSIWYG rich-text editor framework for Leptos — pure Rust at runtime, no JavaScript bridge.
taino-edit is a ProseMirror/TipTap-inspired editor — typed document model,
invertible transforms, history, commands and a Leptos component — built
reactive-first for Rust web frameworks. Unlike leptos-tiptap (a
wasm-bindgen wrapper around the TypeScript TipTap bundle), there is no
JS dependency at runtime.
It is part of the taino-* family, following taino-dnd-*.
Six crates on crates.io. v0.2 closes the v0.1 list UX gaps and broadens the platform (Plugin trait, Markdown round-trip, Dioxus adapter ships real functionality). Tests pass workspace-wide:
| Host tests | 145 (model, schema, content automaton, replace, steps, transforms, state, history, commands, keymap, input-rules, plugin registry, Markdown serializer + parser, and 12 extensions including the full list UX) |
| Browser tests | 52 wasm_bindgen_test cases in headless Chromium 148 (mount, diff/patch, selection sync, DOM-typing → Transform, IME, clipboard, drag/drop, focus, decorations, Leptos component + event wiring) |
See DESIGN_NOTES.md for the architecture, the scope budget, and the resolved design decisions; ROADMAP.md tracks phase progress and what's deferred to v0.2.
- Complete list UX: smart Enter inside a list item splits the
list_item (new bullet below), empty bullet + Enter exits the list,
Tabnests under the previous sibling,Shift-Tablifts out, and multi-item lift now preserves surviving siblings. Plugintrait +PluginKey+ typed-state registry incore: third-party stateful components (word counters, autosave, future CRDT bridges) plug intoEditorStatewithout forking core.- Markdown round-trip:
taino_edit_core::markdown::{to_markdown, parse_markdown}+EditorView::paste_markdown; the Leptos adapter preferstext/markdownon paste when the clipboard advertises it. taino-edit-dioxusships as a real, minimum-viable adapter (mount + DOM patch on signal change).examples/basic-dioxusbuilds withdx serve.
- A typed, immutable document tree (ProseMirror-style
Node/Mark/Fragment/Slice). - A
Schema+SchemaBuilderwith a Thompson-NFA-to-DFA content automaton (paragraph+,(text | image)*,+ * ?). - Schema-checked JSON round-trip (
Node::to_json↔Schema::node_from_json). - A dependency-free escaped HTML serializer and a strict, depth-bounded HTML parser (rejects unknown tags, can't be tricked into injecting markup).
- Invertible, mappable Steps (
ReplaceStep,ReplaceAroundStep,AddMark/RemoveMark/AttrStep),Mappingwith mirror/recover, and aTransformbuilder. - An
EditorStatewithSelection,Transaction, and a bounded groupable undo/redoHistory. - A standard command vocabulary (
select_all,toggle_mark,set_block_type,wrap_in,lift,split_block,join_…, …), a cross-platformKeymap(Mod= Ctrl/Cmd) and abase_keymap. - Regex input rules (
##→ heading,>→ blockquote, …). - A real
contenteditableDOM bridge (taino-edit-dom): mount, incremental diff/patch, bidirectional selection sync, IME composition, clipboard paste sanitized through the schema, drag-and-drop primitives, focus management and node decorations. - A first-class Leptos adapter:
<TainoEditor state=signal />mounts the editor, wires every event (includingselectionchange) back through the state signal, and is tested inside the real Leptos CSR runtime. - Twelve built-in extensions, enough to drop into a real project:
- Inline marks:
Bold(Mod-b),Italic(Mod-i),Link(set_link/remove_linkcommands; the host wires the URL prompt). - Block nodes:
Paragraph(Mod-Alt-0),HeadingH1–H3 (Mod-Alt-1..3),Blockquote(Mod->),CodeBlock(Mod-`), and theListstrio (BulletList/OrderedListListItem,Mod-Shift-8/Mod-Shift-7+Shift-Tabto lift).
- Inline atoms:
Image(insert_imagecommand). - Attribute / selection commands:
Align(align_left/center/right/justify,Mod-Shift-{l,e,r,j}),TransformCase(to_uppercase/to_lowercase). - Undo/redo:
History(Mod-z/Mod-Shift-z).
- Inline marks:
Explicitly deferred to v0.2: generic plugin registry, inline-range
decorations, a richer per-node NodeView trait, the Dioxus adapter,
loro CRDT integration behind a collab feature, Markdown
serializer/parser, smart Enter / nested-list sink (indent) for lists,
and richer extensions (tables, footnotes, mentions, math).
| Crate | Role |
|---|---|
taino-edit-core |
Framework-agnostic model, transforms, state, history, commands, keymap, input rules |
taino-edit-dom |
contenteditable/DOM bridge (web-sys, wasm-bindgen, js-sys) |
taino-edit-extensions |
Bold/Italic/Heading/Paragraph/History, plus the Extension trait |
taino-edit-leptos |
Leptos adapter (<TainoEditor>); first-class for v0.1 |
taino-edit-dioxus |
Placeholder, reserved for v0.2 |
taino-edit |
Umbrella crate, feature-gated re-exports |
Examples under examples/:
basic-leptos— atrunk serve-buildable demo with Bold/Undo/Redo buttons and a mounted editor.headless-core— server-side / CLI demo provingtaino-edit-coreruns identically without a DOM.
[dependencies]
taino-edit = { version = "0.2", features = ["leptos"] } # or "dioxus"No adapter is enabled by default — pick leptos or dioxus.
use leptos::prelude::*;
use taino_edit_leptos::{
build_keymap_with, build_schema_with, Bold, DomSpec, EditorState,
Italic, NodeSpec, SchemaBuilder, TainoEditor,
};
#[component]
fn App() -> impl IntoView {
// Compose a schema on top of the universal doc/text primitives.
let base = SchemaBuilder::new()
.node("doc", NodeSpec { content: Some("block+".into()), ..Default::default() })
.node("text", NodeSpec { group: Some("inline".into()), ..Default::default() });
// Paragraph etc. come from `taino-edit-extensions`.
let exts: Vec<&dyn taino_edit_extensions::Extension> =
vec![&taino_edit_extensions::Paragraph, &Bold, &Italic];
let schema = build_schema_with(base, &exts, "doc").unwrap();
let txt = schema.text("Hello from Rust!", vec![]).unwrap();
let para = schema.node("paragraph", Default::default(), vec![txt], vec![]).unwrap();
let doc = schema.node("doc", Default::default(), vec![para], vec![]).unwrap();
let state = RwSignal::new(EditorState::new(doc, schema));
view! { <TainoEditor state=state /> }
}Requires the Rust toolchain pinned in rust-toolchain.toml
(stable, MSRV 1.80).
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo doc --no-deps --all-featuresBrowser tests for taino-edit-dom and taino-edit-leptos use a small
locally-patched wasm-bindgen-cli; first time only run
./scripts/install-wasm-test-runner.sh, after that
./scripts/wasm-test.sh runs them in headless Chromium 148. See
vendor/README.md for the rationale.
See CONTRIBUTING.md. The roadmap marks community contribution surfaces (the Dioxus adapter, richer extensions, native renderers) explicitly.
Dual-licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.