tiptap-rs 0.1.6

Wasm bindings for Tiptap
Documentation

We're dsplce.co, check out our work on our website: dsplce.co πŸ–€

tiptap-rs

Tiptap WebAssembly crates.io Downloads crates.io Size License crates.io

✍️ Type-safe Wasm bindings for the Tiptap headless rich text editor.

tiptap-rs mirrors Tiptap's original JavaScript API as faithfully as possible, so a transition from JS feels like a transliteration rather than a rewrite β€” whilst handing you an idiomatic, type-safe Rust experience on top.

Disclaimer: this project has no affiliation with the official Tiptap project or trademark.

πŸ–€ Features

  • editor.chain().focus().toggle_bold().run() β€” the chained-command API is mirrored 1:1 from Tiptap's JS, so muscle memory carries over and there's nothing new to learn
  • Type-safe EditorOptions β€” configure the editor with a plain Rust struct instead of poking at an untyped JS object and hoping for the best
  • StarterKit in, batteries included β€” the same common-extensions bundle you'd reach for in JS, one enum variant away
  • CustomExtension(...) β€” bring your own β€” anything Tiptap can load, you can hand straight through; you're not boxed into what we happened to wrap
  • Headings as one method per level β€” toggle_h1() through toggle_h6(), because .toggleHeading({ level }) doesn't translate cleanly and we'd rather it read like Rust

Table of Contents

βΈ»

πŸ“¦ Installation

cargo

Add the crate to your Cargo.toml:

[dependencies]
tiptap-rs = "0.1"

Or let cargo do it for you:

cargo add tiptap-rs

Setup

tiptap-rs binds to Tiptap running in the browser, so Tiptap itself has to be on the page. Add the following to your HTML <head> to make it available to your Wasm module:

<script type="module">
    import * as Tiptap from "https://esm.sh/@tiptap/core";
    import StarterKit from "https://esm.sh/@tiptap/starter-kit";

    window.Tiptap = Tiptap;
    window.StarterKit = StarterKit;
</script>

The bindings resolve Tiptap and any extension (like StarterKit) off the global object by name β€” so whatever you want to use from Rust, expose it on window here first.

βΈ»

πŸ§ͺ Usage

Create an editor

use tiptap_rs::prelude::*;
use gloo::utils::document;

let element = document
    .query_selector(".editor")
    .unwrap()
    .unwrap();

let options = EditorOptions {
    element,
    content: "<p>Hello from Rust!</p>".to_string(),
    extensions: vec![StarterKit],
};

let editor = Editor::new(options);

Chained commands

Tiptap's original API is faithfully mirrored, so the chained commands read just like they do in JS:

// Toggle bold formatting
editor.chain().focus().toggle_bold().run();

// Toggle italic formatting
editor.chain().focus().toggle_italic().run();

// Toggle a paragraph block
editor.chain().focus().toggle_paragraph().run();

Toggling headings

Headings are the one place we deviate from the JS API on purpose: instead of .toggleHeading({ level: 1 }) you get one method per level, so it stays type-safe and reads like Rust:

editor.chain().focus().toggle_h1().run();
editor.chain().focus().toggle_h2().run();
// ... through toggle_h6()

Custom extensions

StarterKit covers the common cases, but you're not limited to it. Expose any Tiptap extension on the window in your setup script:

<script type="module">
    import Highlight from "https://esm.sh/@tiptap/extension-highlight";
    window.Highlight = Highlight;
</script>

then hand it through with CustomExtension, which resolves it off the global object by name:

use tiptap_rs::prelude::*;
use wasm_bindgen::JsValue;
use web_sys::js_sys::{global, Reflect};

let highlight = Reflect::get(&global(), &JsValue::from_str("Highlight")).unwrap();

let options = EditorOptions {
    element,
    content: "<p>Hello from Rust!</p>".to_string(),
    extensions: vec![StarterKit, CustomExtension(highlight)],
};

let editor = Editor::new(options);

Handling button events

Connect editor commands to UI buttons:

use wasm_bindgen::prelude::*;

let editor_clone = editor.clone();
let callback = Closure::wrap(Box::new(move |_| {
    editor_clone.chain().focus().toggle_bold().run();
}) as Box<dyn FnMut(_)>);

let bold_button = document.query_selector(".bold-button").unwrap().unwrap();
bold_button
    .add_event_listener_with_callback("click", callback.as_ref().unchecked_ref())
    .unwrap();
callback.forget();

βΈ»

πŸ“ API Reference

Editor

The main editor instance, wrapping Tiptap's Editor class.

Editor::new(options: EditorOptions) -> Editor

Creates a new editor instance with the specified options. Editor is Clone, so you can hand copies into event callbacks.

EditorOptions

Configuration struct for instantiating an editor:

pub struct EditorOptions {
    pub element: Element,
    pub content: String,
    pub extensions: Vec<Extension>,
}

Extension

The extension set passed to an editor:

pub enum Extension {
    StarterKit,
    CustomExtension(JsValue),
}
  • StarterKit β€” Tiptap's bundle of common extensions, resolved off the global object by name.
  • CustomExtension(JsValue) β€” any other Tiptap extension you've exposed on window (see Custom extensions).

Both variants are re-exported from the prelude, so StarterKit and CustomExtension(...) are in scope directly.

Chained commands

Currently supported on ChainedCommands:

  • toggle_bold()
  • toggle_italic()
  • toggle_strike()
  • toggle_paragraph()
  • toggle_bullet_list()
  • toggle_ordered_list()
  • toggle_h1() through toggle_h6()
  • focus()
  • run() β€” execute the command chain (returns bool)

Start a chain with editor.chain().

βΈ»

πŸš€ Examples

Run the bundled example:

cargo make serve

This requires cargo-make; it pulls in trunk on first run to build and serve the Wasm.

The example demonstrates:

  • Basic editor setup
  • Formatting commands wired to UI buttons
  • Extension usage with StarterKit

βΈ»

πŸ› οΈ Requirements

  • A wasm32-unknown-unknown target β€” tiptap-rs only makes sense compiled to Wasm and run in a browser. Add it with rustup target add wasm32-unknown-unknown.
  • Tiptap on the page β€” the matching @tiptap/* modules exposed on window (see Setup); the bindings call into them at runtime.
  • A bundler that loads it all β€” anything that ships your Wasm alongside the HTML works; the example uses trunk.

βΈ»

πŸ“ Repo & Contributions

πŸ“¦ Crate: https://crates.io/crates/tiptap-rs πŸ› οΈ Repo: https://github.com/dsplce-co/tiptap-rs

PRs welcome πŸ–€

βΈ»

πŸ“„ License

MIT or Apache-2.0, at your option.