Documentation & Knowledge-Base Editor
Build a knowledge-base authoring experience with Scribe: headings via heading(n), code blocks, internal links via link(url), localStorage autosave on onChange, and clean HTML via getHTML().
Everything a docs editor needs
Headings & document structure
Call editor.heading(2) and editor.heading(3) to build a clean H1–H6 hierarchy — the backbone of any documentation page and its on-page table of contents.
Code blocks & inline code
Technical docs need code. Scribe supports inline code and code blocks, and getHTML() emits clean semantic markup you can syntax-highlight on render.
Autosave to localStorage
Wire onChange to a debounced save into localStorage so writers never lose a draft. Restore it with setHTML() on load, and clear it once it's committed to the server.
Internal links & clean output
Use editor.link("/docs/api-reference") for internal KB cross-links. getHTML() returns clean, XSS-sanitized HTML safe to store and re-render across your knowledge base.
Docs Editor with Headings & Internal Links
Use heading(n) for structure and link(url) for internal KB cross-links, then publish via getHTML().
import { Scribe } from 'scribejs-editor';
import 'scribejs-editor/dist/scribe.css';
// A docs editor wants a fixed toolbar for long-form structured writing
const editor = Scribe.init('#doc-body', {
toolbar: 'fixed',
autofocus: true,
placeholder: 'Write your documentation...',
onChange: (html) => autosaveLocal(html),
});
// Headings for structure, code blocks, and internal links
document.querySelector('#h2').addEventListener('click', () => editor.heading(2));
document.querySelector('#h3').addEventListener('click', () => editor.heading(3));
// Internal link to another KB article
document.querySelector('#internal-link').addEventListener('click', () => {
const slug = prompt('Article slug (e.g. /docs/api-reference):');
if (slug) editor.link(slug);
});
// Publish clean HTML to your knowledge base
document.querySelector('#publish').addEventListener('click', async () => {
const html = editor.getHTML(); // clean semantic HTML, safe to store
await fetch(`/api/kb/${articleId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: html }),
});
});Autosave with onChange + localStorage
Wire onChange to a debounced write into localStorage, then restore the draft with setHTML() on load.
import { Scribe } from 'scribejs-editor';
const STORAGE_KEY = `kb-draft-${articleId}`;
const editor = Scribe.init('#doc-body', {
toolbar: 'fixed',
onChange: scheduleAutosave,
});
// Restore an unsaved local draft on load (localStorage source of truth)
const cached = localStorage.getItem(STORAGE_KEY);
if (cached) editor.setHTML(cached);
// Debounced autosave to localStorage so writers never lose work
let saveTimer;
function scheduleAutosave(html) {
clearTimeout(saveTimer);
document.querySelector('#status').textContent = 'Saving...';
saveTimer = setTimeout(() => {
localStorage.setItem(STORAGE_KEY, html);
document.querySelector('#status').textContent = 'Saved locally';
}, 1500);
}
// Clear the local draft once it is committed to the server
async function publish() {
const html = editor.getHTML();
await fetch(`/api/kb/${articleId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: html }),
});
localStorage.removeItem(STORAGE_KEY);
}React Documentation Editor Component
A complete React component with localStorage autosave, draft restore, and publish.
import { useRef, useEffect, useState, useCallback } from 'react';
import { Scribe } from 'scribejs-editor';
export function DocsEditor({ articleId, initialContent }) {
const containerRef = useRef(null);
const editorRef = useRef(null);
const saveTimer = useRef(null);
const [status, setStatus] = useState('');
const storageKey = `kb-draft-${articleId}`;
useEffect(() => {
editorRef.current = Scribe.init(containerRef.current, {
toolbar: 'fixed',
autofocus: true,
placeholder: 'Write your documentation...',
onChange: scheduleAutosave,
});
// Prefer a local draft, fall back to the server content
const cached = localStorage.getItem(storageKey);
editorRef.current.setHTML(cached ?? initialContent ?? '');
return () => editorRef.current?.destroy();
}, [articleId]);
const scheduleAutosave = useCallback((html) => {
clearTimeout(saveTimer.current);
setStatus('Saving...');
saveTimer.current = setTimeout(() => {
localStorage.setItem(storageKey, html);
setStatus('Saved locally');
}, 1500);
}, [storageKey]);
const publish = async () => {
const html = editorRef.current?.getHTML();
if (!html) return;
await fetch(`/api/kb/${articleId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: html }),
});
localStorage.removeItem(storageKey);
setStatus('Published');
};
return (
<div className="docs-editor">
<div className="docs-toolbar">
<span className="status">{status}</span>
<button onClick={publish}>Publish</button>
</div>
<div ref={containerRef} className="prose prose-lg max-w-none" />
</div>
);
}Everything a docs author expects
Ship your documentation editor today.
Headings, code blocks, internal links, autosave, clean HTML. Everything in 50KB — free under MIT.
Documentation & KB editor — common questions
How do I build a documentation editor in JavaScript?
Initialize Scribe Editor with toolbar: 'fixed' for long-form writing, use editor.heading(n) for structure, support code blocks and inline code, and use editor.link('/docs/...') for internal cross-links. On publish, call editor.getHTML() to get clean semantic HTML. Scribe is under 50KB gzipped, MIT-licensed, and works with React, Vue 3, Svelte, and Vanilla JS.
How do I add autosave with localStorage to a docs editor?
In the Scribe onChange callback, clear a previous timeout and set a new one (around 1.5 seconds). When it fires, write editor.getHTML() to localStorage under a per-article key. On load, restore the draft with editor.setHTML(cached). Remove the localStorage key once the article is committed to the server so the local draft doesn't override published content.
Can Scribe Editor handle headings and code blocks for documentation?
Yes. Use editor.heading(2) through editor.heading(6) to build a heading hierarchy, plus inline code and code blocks for technical content. getHTML() emits clean semantic HTML, so you can apply your own syntax highlighting and table-of-contents generation when rendering the published article.
Does Scribe produce clean HTML safe to store in a knowledge base?
Yes. editor.getHTML() returns clean, semantic HTML with built-in XSS sanitization, and paste handling strips proprietary Word and Google Docs markup. That means the documentation HTML you store stays consistent and safe to re-render across your knowledge base.
Also compare Scribe with:
Use Scribe in your framework:
Popular use cases: