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, documentededitor.pm.view// EditorVieweditor.pm.state// EditorStateeditor.pm.dispatch(tr)// Dispatch transactionseditor.pm.setNodeAttrs(pos,attrs)// ❌ Other editors - hacky, undocumented(editorasany).prosemirrorView(editorasany)._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-reactimport{useOpenBlock,OpenBlockView,SlashMenu,BubbleMenu,TableHandles}from'@labbs/openblock-react';// CSS is auto-injected by default - no import needed!functionEditor(){consteditor=useOpenBlock({initialContent: [{id: '1',type: 'paragraph',props: {},content: [{type: 'text',text: 'Hello, World!',styles: {}}]}],});return(<><OpenBlockVieweditor={editor}/><SlashMenueditor={editor}/><BubbleMenueditor={editor}/><TableHandleseditor={editor}/></>);}import{OpenBlockEditor}from'@labbs/openblock-core';// CSS is auto-injected by default - no import needed!consteditor=newOpenBlockEditor({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 blockseditor.setDocument(blocks)// Replace documenteditor.getBlock(id)// Find block by IDeditor.insertBlocks(blocks,ref,pos)// Insert blockseditor.updateBlock(id,update)// Update blockeditor.removeBlocks(ids)// Delete blockseditor.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 tableif(isInTable(editor.pm.state)){// Add row after current rowaddRowAfter(editor.pm.state,editor.pm.view.dispatch);// Add column before current columnaddColumnBefore(editor.pm.state,editor.pm.view.dispatch);// Delete current rowdeleteRow(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// EditorVieweditor.pm.state// EditorStateeditor.pm.doc// Documenteditor.pm.dispatch(tr)// Dispatch transactioneditor.pm.setNodeAttrs(pos,attrs)interfaceBlock{id: string;type: string;props: Record<string,unknown>;content?: InlineContent[];children?: Block[];}// Example documentconstblocks=[{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<TableHandleseditor={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 blockconstAlertBlock=createReactBlockSpec({type: 'alert',propSchema: {alertType: {default: 'info'},message: {default: ''},},content: 'none',},{render: ({ block })=>(<divclassName={`alert alert-${block.props.alertType}`}>{block.props.message}</div>),slashMenu: {title: 'Alert',description: 'Insert an alert box',aliases: ['warning','info'],group: 'Basic',},});// Use custom blocksconstCUSTOM_BLOCKS=[AlertBlock];functionEditor(){consteditor=useOpenBlock({customBlocks: CUSTOM_BLOCKS});constcustomItems=useCustomSlashMenuItems(editor,CUSTOM_BLOCKS);return(<><OpenBlockVieweditor={editor}/><SlashMenueditor={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