tiptap_flutter 0.0.1
tiptap_flutter: ^0.0.1 copied to clipboard
A Flutter port of the Tiptap rich-text editor, powered by a headless WebView engine. Provides composable widgets and a controller-based API for building rich-text editing experiences.
⚠️ Alpha Release — Not recommended for production use. This package is in early development. APIs will change, features are incomplete, and there are known limitations. It's published so developers can evaluate the approach and provide feedback.
tiptap_flutter #
A Flutter rich-text editor powered by the real Tiptap engine running inside a headless WebView. Every pixel on screen is rendered by Flutter — the WebView serves purely as a computation engine.
Why this exists #
If your web app uses Tiptap, your content is stored in Tiptap's document format — a JSON structure built on ProseMirror's document model. When you build a Flutter app for the same product, you need to read, render, and edit that same content. A from-scratch Dart editor won't understand Tiptap's format, extensions, or schema. You'd have to reimplement the entire ProseMirror document model, every extension's behavior, and keep it all in sync with the web app.
Porting ProseMirror to Dart would take years and kill compatibility with Tiptap's extension ecosystem. Instead of porting the code, we run the original code.
tiptap_flutter loads unmodified Tiptap JavaScript inside a headless WebView. The engine handles all document operations — parsing, schema validation, commands, transactions, undo/redo — exactly as it does on the web. Flutter handles all rendering, input, and UI. Your Flutter app and web app share the same document format, the same extensions, and the same behavior, because they're running the same engine.
How it compares to existing approaches #
Most Flutter rich-text editors (super_editor, flutter_quill, appflowy_editor) implement their own document models from scratch. They work well for apps that start fresh, but they can't read or write Tiptap documents without a translation layer that inevitably loses fidelity.
Some React Native solutions (like 10tap-editor) render ProseMirror in a visible WebView. That gives you the real engine, but also WebView jank — keyboard issues, scroll conflicts, focus fighting, and a non-native feel.
tiptap_flutter takes a different path: real engine, native rendering. The WebView is invisible. Zero pixels from it reach the screen. All rendering, gesture handling, text input, and selection painting are done by Flutter widgets. You get engine compatibility without the WebView UX problems.
Architecture #
The package is split into two parts:
-
tiptap-engine — a standalone JavaScript bundle that runs unmodified Tiptap inside a headless WebView. It handles all document operations: parsing, schema validation, commands, transactions, undo/redo. This is a separate project and can be used by ports for other frameworks beyond Flutter.
-
tiptap_flutter — the Flutter port. It renders the document as native Flutter widgets, handles gestures and keyboard input, paints selections and cursors, and communicates with the engine over a JSON message bridge.
Commands flow down as JSON. Events and responses flow back up through the same channel, correlated by command ID. The controller caches state for synchronous access and exposes streams for reactive UI updates.
Quick start #
1. Add the dependency #
dependencies:
tiptap_flutter: ^0.0.1
2. Create a controller and initialize #
final controller = EditorController();
await controller.initialize(
content: '<h1>Hello</h1><p>Start editing...</p>',
);
3. Compose the widgets #
Scaffold(
body: Column(
children: [
TiptapToolbar(controller: controller),
Expanded(
child: TiptapEditor(controller: controller),
),
],
),
)
4. Clean up #
@override
void dispose() {
controller.dispose();
super.dispose();
}
Composable API #
The package follows the same philosophy as Tiptap React — a headless core with composable UI components.
EditorController is the center of everything. It manages the engine lifecycle, sends commands, and exposes state streams. All widgets take a controller.
TiptapEditor is the content area — document rendering, gesture handling, selection painting, and keyboard input. This is the only required widget.
TiptapToolbar is a standalone formatting toolbar that listens to the controller and rebuilds when state changes. Place it anywhere.
DebugOverlay is an opt-in development tool for inspecting editor state, document JSON, selection, and bridge logs.
For full control, skip the provided widgets and build your own UI using the controller's streams and methods directly:
controller.editorStateStream.listen((state) {
// state.doc — the full document tree
// state.selection — current selection
// state.activeMarks — active mark types
// state.commandStates — all command states
});
await controller.execCommand('toggleBold');
await controller.execCommand('setHeading', {'level': 2});
await controller.setContent('<p>New content</p>');
final html = await controller.getHTML();
final json = await controller.getJSON();
Reading and writing content #
The editor supports full round-trip content — load a Tiptap document, let the user edit it, and get the modified content back in any format.
// Initialize with HTML or Tiptap JSON
await controller.initialize(content: '<h1>Hello</h1><p>Edit me</p>');
// Get the current content at any point
final html = await controller.getHTML();
final json = await controller.getJSON();
final text = await controller.getText();
// Replace the entire document
await controller.setContent('<p>Something new</p>');
To react to every change as the user types, listen to the editor state stream:
controller.editorStateStream.listen((state) {
// Fires on every transaction — typing, formatting, undo, etc.
// state.doc contains the full annotated document tree
controller.getJSON().then((json) => saveToBackend(json));
});
This makes it straightforward to keep a backend in sync, build autosave, or wire up to any state management solution.
Custom node renderers #
Register custom renderers for any node type:
NodeRendererRegistry.defaultRegistry.register('myCustomNode', (node, childBuilder, registry) {
return MyCustomWidget(
data: node.attrs,
children: node.content?.map(childBuilder).toList(),
);
});
Platform support #
| Platform | Status |
|---|---|
| Android | ✅ Supported |
| iOS | ✅ Supported |
| Web | ❌ Not applicable (use Tiptap directly) |
| Desktop | ❌ Not supported (requires WebView) |
Known limitations #
This is an alpha release. Known issues include:
- No clipboard support (copy/paste)
- No image handling beyond basic display
- No collaborative editing support yet
- No decoration rendering (highlights, search matches)
- Table editing is limited
- Hardware keyboard shortcuts beyond backspace and enter are not yet handled
- Performance with very large documents has not been optimized
- The engine assets add approximately 1MB to the app bundle
Engine assets #
The Tiptap engine JavaScript bundle is included in the package. No npm or Node.js is required to use it.
The engine is maintained separately at tiptap-engine. If you need to rebuild it with custom extensions:
git clone https://github.com/blackcoffee2/tiptap-engine.git
cd tiptap-engine
npm install
npm run build
License #
MIT