free-block-editor
v0.3.0
Published
Notion-style block editor for React — slash menu, markdown shortcuts & round-trip, syntax-highlighted code blocks, tables, images, drag reorder
Maintainers
Readme
free-block-editor
A lightweight, dependency-free Notion-style block editor for React.
- 🧱 12 block types — paragraph, headings (1–3), bulleted / numbered lists, to-do (checkbox), quote, code, image (URL), table, divider
- ⚡ Slash menu — type
/to insert any block, with keyword search (English & Korean built in) - ⌨️ Markdown shortcuts —
#,##,-,1.,[],>,```,---convert as you type - 🔁 Markdown round-trip — serialize blocks to GitHub-flavored Markdown and parse it back (
blocksToMarkdown/markdownToBlocks) - 🎨 Syntax-highlighted code blocks — 10 languages out of the box, live highlighting while typing, extensible single-file language registry
- 🖼 Image blocks — URL based; pasting an image URL into an empty block auto-converts it
- ▦ Tables — header row, Tab/Enter cell navigation, add/remove rows & columns, GFM table round-trip
- 🖱 Drag to reorder, keyboard navigation between blocks, safe plain-text paste
- ↩️ Undo / redo — block-level history (
Ctrl+Z/Ctrl+Shift+Z/Ctrl+Y), consecutive typing merged into one step - ✨ Inline formatting — select text to get a floating toolbar (bold / italic / strike / code / link,
Ctrl+B/Ctrl+I); inline markdown is live-styled as you type with dimmed markers, IME-safe - 📤 Image upload hook — provide
onUploadImageand get paste-to-upload + a file picker for free - 🟦 TypeScript — full type definitions included
- 📦 Zero runtime dependencies — only
react/react-dompeers
Install
npm install free-block-editorQuick start
import { useState } from 'react';
import { BlockEditor, BlockPreview, makeBlock } from 'free-block-editor';
import 'free-block-editor/styles.css';
export default function App() {
const [blocks, setBlocks] = useState([makeBlock('h1', 'Hello'), makeBlock()]);
return (
<>
<BlockEditor blocks={blocks} onChange={setBlocks} />
{/* read-only rendering */}
<BlockPreview blocks={blocks} />
</>
);
}Image upload
Pass onUploadImage to enable file uploads — it is called when the user pastes an
image file or picks one from the image block's file button. Resolve with a URL:
<BlockEditor
blocks={blocks}
onChange={setBlocks}
onUploadImage={async (file) => {
const form = new FormData();
form.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: form });
return (await res.json()).url;
}}
/>Without the prop, image blocks are URL-input only.
Markdown round-trip
import { blocksToMarkdown, markdownToBlocks } from 'free-block-editor';
const md = blocksToMarkdown(blocks); // blocks -> GFM string
const back = markdownToBlocks(md); // GFM string -> blocksBlock data model
Blocks are plain JSON — store them anywhere:
{
"id": "uuid",
"type": "text | h1 | h2 | h3 | bullet | number | todo | quote | code | image | table | divider",
"text": "content",
"props": {
"checked": false,
"language": "javascript",
"url": "https://…", "alt": "",
"rows": [["header"], ["cell"]]
}
}Customization
Theme (CSS variables)
All colors are CSS custom properties scoped to the editor root — override them in your own stylesheet:
.bek-editor, .bek-preview {
--bek-text: #222;
--bek-accent: #e91e63;
--bek-border: #ddd;
--bek-hover: rgba(0, 0, 0, 0.05);
--bek-surface: #fafafa;
}Code languages & highlight colors
The language registry is a single array — add an entry and it appears in the dropdown with highlighting and theming applied automatically:
import { LANGUAGES, DEFAULT_THEME } from 'free-block-editor';
LANGUAGES.push({
id: 'go',
label: 'Go',
theme: { accent: '#00add8', keyword: '#ff7b72' }, // partial override of DEFAULT_THEME
patterns: [ // ordered token rules
{ type: 'comment', match: /\/\/[^\n]*|\/\*[\s\S]*?\*\// },
{ type: 'string', match: /"(?:[^"\\]|\\[\s\S])*"|`[^`]*`/ },
{ type: 'number', match: /\b\d+(?:\.\d+)?\b/ },
{ type: 'keyword', match: /\b(?:func|package|import|go|defer|chan|select|return|if|else|for|range|var|const|type|struct|interface|map|nil|true|false)\b/ },
{ type: 'function', match: /[A-Za-z_]\w*(?=\s*\()/ },
],
});Token types: comment string number keyword function operator — each maps to a color key of the same name in theme / DEFAULT_THEME.
Slash menu / block types
BLOCK_TYPES, MARKDOWN_SHORTCUTS, and PLACEHOLDERS are exported for inspection and label/keyword customization (e.g. localization).
API
| Export | Description |
|---|---|
| BlockEditor | { blocks, onChange, onUploadImage? } — the editable surface |
| BlockPreview | { blocks } — read-only renderer |
| blocksToMarkdown(blocks) | serialize to GFM |
| markdownToBlocks(md) | parse GFM to blocks |
| makeBlock(type?, text?, props?) | create a block with a fresh id |
| renderInline(text) | inline markdown (**bold**, `code`, links…) to HTML string |
| LANGUAGES, DEFAULT_THEME, getLanguage, themeVars, highlight | code language registry & highlighter |
| BLOCK_TYPES, MARKDOWN_SHORTCUTS, PLACEHOLDERS | block type definitions |
