A fully open-source block editor with public ProseMirror API
Features • Quick Start • API • Docs • Live Demo
Unlike other editors (BlockNote, Tiptap), OpenBlock exposes all ProseMirror internals through a typed, documented API. No more hacks.
// ✅ OpenBlock - clean, typed, documented editor.pm.view // EditorView editor.pm.state // EditorState editor.pm.dispatch(tr) // Dispatch transactions editor.pm.setNodeAttrs(pos, attrs) // ❌ Other editors - hacky, undocumented (editor as any).prosemirrorView (editor as any)._tiptapEditor.view
- Public ProseMirror API — Full access via
editor.pm.* - Block-based JSON — Notion-like format, easy to store and transform
- Framework-agnostic — Vanilla JS core with React bindings (Vue, Svelte coming)
- TypeScript first — Full type safety throughout
- Rich block types — Headings, lists, code blocks, tables, columns, and more
- Built-in UI — Slash menu, bubble menu, drag & drop, table handles
First, configure npm to use GitHub Packages for the @labbs scope. Create or edit .npmrc in your project:
@labbs:registry=https://npm.pkg.github.com
Then install the packages:
npm install @labbs/openblock-core @labbs/openblock-react
import { useOpenBlock, OpenBlockView, SlashMenu, BubbleMenu, TableHandles } from '@labbs/openblock-react'; // CSS is auto-injected by default - no import needed! function Editor() { const editor = useOpenBlock({ initialContent: [ { id: '1', type: 'paragraph', props: {}, content: [{ type: 'text', text: 'Hello, World!', styles: {} }] } ], }); return ( <> <OpenBlockView editor={editor} /> <SlashMenu editor={editor} /> <BubbleMenu editor={editor} /> <TableHandles editor={editor} /> </> ); }
import { OpenBlockEditor } from '@labbs/openblock-core'; // CSS is auto-injected by default - no import needed! const editor = new OpenBlockEditor({ initialContent: [/* blocks */], }); editor.mount(document.getElementById('editor'));
Note: CSS styles are automatically injected into the document head. To disable auto-injection and provide your own styles, set
injectStyles: falsein the editor config.
editor.getDocument() // Get all blocks editor.setDocument(blocks) // Replace document editor.getBlock(id) // Find block by ID editor.insertBlocks(blocks, ref, pos) // Insert blocks editor.updateBlock(id, update) // Update block editor.removeBlocks(ids) // Delete blocks
editor.toggleBold() editor.toggleItalic() editor.toggleUnderline() editor.toggleStrikethrough() editor.toggleCode() editor.setTextColor(color) editor.setBackgroundColor(color)
editor.setBlockType('heading', { level: 1 }) editor.setBlockType('codeBlock', { language: 'typescript' }) editor.setBlockType('bulletList') editor.setBlockType('orderedList') editor.setBlockType('blockquote') editor.setBlockType('table')
import { addRowAfter, addRowBefore, deleteRow, addColumnAfter, addColumnBefore, deleteColumn, goToNextCell, goToPreviousCell, isInTable } from '@labbs/openblock-core'; // Check if cursor is in a table if (isInTable(editor.pm.state)) { // Add row after current row addRowAfter(editor.pm.state, editor.pm.view.dispatch); // Add column before current column addColumnBefore(editor.pm.state, editor.pm.view.dispatch); // Delete current row deleteRow(editor.pm.state, editor.pm.view.dispatch); } // Navigate between cells (Tab / Shift+Tab) goToNextCell(editor.pm.state, editor.pm.view.dispatch); goToPreviousCell(editor.pm.state, editor.pm.view.dispatch);
editor.pm.view // EditorView editor.pm.state // EditorState editor.pm.doc // Document editor.pm.dispatch(tr) // Dispatch transaction editor.pm.setNodeAttrs(pos, attrs)
interface Block { id: string; type: string; props: Record<string, unknown>; content?: InlineContent[]; children?: Block[]; } // Example document const blocks = [ { id: 'heading-1', type: 'heading', props: { level: 1 }, content: [{ type: 'text', text: 'Welcome', styles: { bold: true } }] }, { id: 'para-1', type: 'paragraph', props: {}, content: [{ type: 'text', text: 'Hello world', styles: {} }] }, { id: 'table-1', type: 'table', props: {}, children: [ { id: 'row-1', type: 'tableRow', props: {}, children: [ { id: 'header-1', type: 'tableHeader', props: {}, content: [{ type: 'text', text: 'Name', styles: {} }] }, { id: 'header-2', type: 'tableHeader', props: {}, content: [{ type: 'text', text: 'Value', styles: {} }] } ] }, { id: 'row-2', type: 'tableRow', props: {}, children: [ { id: 'cell-1', type: 'tableCell', props: {}, content: [{ type: 'text', text: 'Item', styles: {} }] }, { id: 'cell-2', type: 'tableCell', props: {}, content: [{ type: 'text', text: '100', styles: {} }] } ] } ] } ];
The main editor component that renders the ProseMirror view.
Displays a command palette when typing / to insert blocks.
Floating toolbar that appears when selecting text for formatting.
Renders handles on tables for adding/removing rows and columns.
import { TableHandles } from '@labbs/openblock-react'; // Add to your editor <TableHandles editor={editor} />
When hovering over a table:
- Row handles appear on the left — click to insert/delete rows
- Column handles appear on top — click to insert/delete columns
- Extend buttons appear on the right and bottom — click to add rows/columns
Create custom React blocks with createReactBlockSpec:
import { createReactBlockSpec, useOpenBlock, SlashMenu, useCustomSlashMenuItems } from '@labbs/openblock-react'; // Define a custom block const AlertBlock = createReactBlockSpec( { type: 'alert', propSchema: { alertType: { default: 'info' }, message: { default: '' }, }, content: 'none', }, { render: ({ block }) => ( <div className={`alert alert-${block.props.alertType}`}> {block.props.message} </div> ), slashMenu: { title: 'Alert', description: 'Insert an alert box', aliases: ['warning', 'info'], group: 'Basic', }, } ); // Use custom blocks const CUSTOM_BLOCKS = [AlertBlock]; function Editor() { const editor = useOpenBlock({ customBlocks: CUSTOM_BLOCKS }); const customItems = useCustomSlashMenuItems(editor, CUSTOM_BLOCKS); return ( <> <OpenBlockView editor={editor} /> <SlashMenu editor={editor} additionalItems={customItems} /> </> ); }
See Custom Blocks Guide for more details.
| Guide | Description |
|---|---|
| React Integration | Hooks, components, and patterns for React |
| Custom Blocks | Creating your own block types |
| Custom Marks | Creating inline formatting marks |
| Plugins | Building ProseMirror plugins |
| Styling | CSS customization and theming |
| Package | Description |
|---|---|
| @labbs/openblock-core | Framework-agnostic editor core |
| @labbs/openblock-react | React bindings and components |
# Install dependencies pnpm install # Build all packages pnpm build # Run example in dev mode pnpm dev