Search page titles, headings and text across the docs.

Get started

On this page

Quick start: Web

In this guide you put a live canvist editor on a web page with @canvist/web, add a toolbar that follows the selection, and set up a bundler. It takes about five minutes.

Status: preview. @canvist/web works in Chromium, Firefox, and Safari, and its API may still change before 1.0. Using React or Vue? See React and Vue.

1. Install

npm install @canvist/web

2. Create an editor

Give the editor an element with a width. Without a height, it grows with its document and the page scrolls:

<div class="toolbar"><button id="bold" type="button">Bold</button></div>
<div id="editor" style="max-width: 640px"></div>
import { createEditor } from "@canvist/web";

const element = document.querySelector<HTMLElement>("#editor");
const bold = document.querySelector<HTMLButtonElement>("#bold");

if (element !== null && bold !== null) {
	const editor = await createEditor(element, {
		content: { format: "markdown", input: "# Hello\n\nStart typing." },
		label: "Notes",
	});

	// Keep the button in step with the selection.
	const refresh = () => {
		bold.disabled = !editor.can.toggleBold();
		bold.setAttribute("aria-pressed", String(editor.query.isBold()));
	};
	editor.subscribe(refresh);
	refresh();

	bold.addEventListener("click", () => {
		editor.commands.toggleBold();
		editor.focus();
	});
}

The editor paints on a canvas, but it types, selects, copies, pastes, and composes text from input methods as a text field does, and screen readers read it. Text input on the web explains how.

Commands, queries, and can checks are typed from the extensions you pass (TipTap’s StarterKit by default). Calling one you didn’t register is a type error.

3. Set up your bundler

@canvist/web loads a WebAssembly module from next to its own files, and bundlers that follow new URL(…, import.meta.url) copy it for you. In Vite, exclude canvist from dependency pre-bundling, which would move the modules away from it:

// vite.config.ts
export default {
	optimizeDeps: { exclude: ["@canvist/web", "@canvist/headless"] },
};

To serve the module yourself, pass its URL as wasm in the options.

4. Hydrate server-rendered HTML

canvist’s server renderer sends the document as HTML that wraps exactly where the editor does. Render it into the editor’s element, and createEditor reads the document from it. It keeps the HTML on screen until your fonts have loaded, then swaps in the canvas in one frame, so nothing moves:

import { createEditor } from "@canvist/web";

// The element already holds the server's `<div class="cv-doc">…</div>`.
const element = document.querySelector<HTMLElement>("#editor");

if (element !== null) {
	const editor = await createEditor(element, {
		// The theme the server rendered with.
		theme: { bodyFamily: "Inter, sans-serif" },
	});
	await editor.ready(); // the canvas is showing now
}

The server and the browser must lay text out with the same font files. Fonts and text rendering shows how.

Run the examples

examples/web in the repository has a plain page, React, Vue, and a server-rendered page, each with a Playwright test:

cd examples/web
npm install
npm run ssr   # renders ssr.html with the Rust renderer
npm run dev
Docs