Quikdown Editor Documentation
Quikdown Editor is a drop-in markdown editor control that provides a complete editing experience with live preview, bidirectional editing, and plugin support.
Table of Contents
- Installation
- Basic Usage
- Configuration Options
- API Reference
- View Modes
- Themes
- Plugin Integration
- Events and Callbacks
- Keyboard Shortcuts
- Examples
- Browser Support
Installation
CDN
<!-- ES Module -->
<script type="module">
import QuikdownEditor from 'https://unpkg.com/quikdown/dist/quikdown_edit.esm.min.js';
</script>
<!-- UMD -->
<script src="https://unpkg.com/quikdown/dist/quikdown_edit.umd.min.js"></script>NPM
npm install quikdown// ES Module
import QuikdownEditor from 'quikdown/edit';
// CommonJS
const QuikdownEditor = require('quikdown/edit');Basic Usage
Minimal Setup
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Quikdown Editor</title>
</head>
<body>
<div id="editor" style="height: 400px;"></div>
<script type="module">
import QuikdownEditor from 'https://unpkg.com/quikdown/dist/quikdown_edit.esm.min.js';
const editor = new QuikdownEditor('#editor');
editor.setMarkdown('# Hello World\n\nStart editing!');
</script>
</body>
</html>With Options
const editor = new QuikdownEditor('#editor', {
mode: 'split', // View mode
theme: 'dark', // Theme
showToolbar: true, // Show toolbar
lazy_linefeeds: false, // Lazy linefeeds
debounceDelay: 300, // Debounce delay
placeholder: 'Type here...',
plugins: {
highlightjs: true, // Enable syntax highlighting
mermaid: true // Enable Mermaid diagrams
}
});Configuration Options
| Option | Type | Default | Description | ||
|---|---|---|---|---|---|
mode |
string | 'split' |
Initial view mode: 'source', 'split', or 'preview' |
||
theme |
string | 'auto' |
Theme: 'light', 'dark', or 'auto' (follows system) |
||
showToolbar |
boolean | true |
Show/hide the toolbar | ||
showRemoveHR |
boolean | false |
Show/hide the "Remove HR" button in toolbar | ||
lazy_linefeeds |
boolean | false |
Enable lazy linefeeds (single \n becomes <br>) |
||
debounceDelay |
number | 20 |
Debounce delay in milliseconds for updates | ||
placeholder |
string | 'Start typing markdown...' |
Placeholder text for empty editor | ||
initialContent |
string | '' |
Initial markdown content | ||
plugins |
object | {} |
Built-in plugin configuration (legacy — prefer preloadFences) |
||
preloadFences |
string \ | array \ | null | null |
Preload fence libraries at construction (see below) |
customFences |
object | {} |
Custom fence plugin handlers | ||
showLazyLinefeeds |
boolean | false |
Show "Fix Linefeeds" button in toolbar | ||
showUndoRedo |
boolean | false |
Show undo/redo buttons in toolbar | ||
undoStackSize |
number | 100 |
Maximum undo states to keep | ||
onChange |
function | null |
Callback when content changes | ||
enableComplexFences |
boolean | true |
Enable complex fence rendering (mermaid, math, geojson, stl, etc.) | ||
inline_styles |
boolean | false |
Embed styles inline instead of using class-based CSS | ||
onModeChange |
function | null |
Callback when mode changes |
Plugin Options (legacy)
plugins: {
highlightjs: boolean, // Enable Highlight.js for syntax highlighting
mermaid: boolean // Enable Mermaid for diagrams
}preloadFences — fence library preloading
Each fence type that depends on an external library (Mermaid, MathJax, Leaflet
for GeoJSON, Three.js for STL, highlight.js for syntax highlighting) is
lazy-loaded by default. The first time the editor encounters a mermaid
fence, it fetches mermaid.min.js from the CDN; the first STL fence triggers
the Three.js download; and so on.
Lazy loading keeps the editor lightweight (~70 KB minified) but introduces a
small "loading…" delay the first time each fence type is rendered. For demo
pages, documentation sites, or apps where you know in advance which fence
types you'll need, you can preload those libraries at construction time:
// Preload everything — first render of any fence is instant
new QuikdownEditor('#editor', {
preloadFences: 'all'
});
// Preload a specific subset
new QuikdownEditor('#editor', {
preloadFences: ['mermaid', 'math', 'highlightjs']
});
// Preload a custom library
new QuikdownEditor('#editor', {
preloadFences: [
'mermaid',
{ name: 'mermaid-extra', script: 'https://my-cdn/mermaid-extra.js', css: 'https://my-cdn/mermaid-extra.css' }
]
});
// Default — lazy load on demand
new QuikdownEditor('#editor', {});Recognized library names:
| Name | Loads | Used by |
|---|---|---|
'highlightjs' |
highlight.js + GitHub theme | code-block syntax highlighting |
'mermaid' |
Mermaid.js | mermaid fence |
'math' |
MathJax (tex-svg) | math, tex, latex, katex fences |
'geojson' |
Leaflet + Leaflet CSS | geojson fence |
'stl' |
Three.js | stl fence |
Trade-off: preloading uses more upfront network (a few hundred KB) but
eliminates per-fence load delays. Developer's choice — the editor itself
stays the same size either way; only the optional fence renderers are affected.
Recommendation: leave preloadFences off for production apps that handle
arbitrary user content (lazy loading scales better), and set it to 'all'
or a specific list for demos and apps where you know exactly which fence
types will appear.
API Reference
Constructor
new QuikdownEditor(container, options?)Parameters:
container(string | HTMLElement): CSS selector or DOM elementoptions(object): Configuration options
Returns: QuikdownEditor instance
Methods
setMarkdown(markdown)
Sets the markdown content.
editor.setMarkdown('# New Content');getMarkdown()
Returns the current markdown content.
const markdown = editor.getMarkdown();getHTML()
Returns the rendered HTML.
const html = editor.getHTML();setMode(mode)
Changes the view mode.
editor.setMode('preview'); // 'source', 'split', or 'preview'removeHR()
Removes all horizontal rules (---) from the markdown content.
editor.removeHR();copyRendered()
Copies the rendered HTML content to clipboard as rich text. Converts math equations to images and GeoJSON maps to static images for better paste compatibility.
editor.copyRendered();setLazyLinefeeds(enabled)
Enables or disables lazy linefeeds (single \n becomes <br>).
editor.setLazyLinefeeds(true);setDebounceDelay(delay)
Sets the debounce delay for input updates (in milliseconds). Use 0 for instant updates.
editor.setDebounceDelay(0); // Instant updates
editor.setDebounceDelay(300); // Defaultundo()
Undoes the last edit.
editor.undo();redo()
Redoes the last undone edit.
editor.redo();canUndo()
Returns true if there is an edit to undo.
if (editor.canUndo()) {
editor.undo();
}canRedo()
Returns true if there is an edit to redo.
if (editor.canRedo()) {
editor.redo();
}clearHistory()
Clears the undo/redo history.
editor.clearHistory();getLazyLinefeeds()
Returns the current lazy linefeeds state (true or false).
const isLazy = editor.getLazyLinefeeds();convertLazyLinefeeds()
Converts single newlines in the current content to paragraph breaks (\n\n). Useful for normalizing content that was authored with lazy linefeeds enabled.
editor.convertLazyLinefeeds();destroy()
Cleans up the editor and removes event listeners.
editor.destroy();Static Methods
QuikdownEditor.convertLazyLinefeeds(markdown)
Converts single newlines to paragraph breaks in the given markdown string. Returns the converted string.
const normalized = QuikdownEditor.convertLazyLinefeeds('Line one\nLine two');
// "Line one\n\nLine two"QuikdownEditor.removeHRFromMarkdown(markdown)
Removes all horizontal rules (---) from the given markdown string. Returns the cleaned string.
const cleaned = QuikdownEditor.removeHRFromMarkdown('Some text\n\n---\n\nMore text');Properties
markdown
Get or set markdown content.
// Get
const content = editor.markdown;
// Set
editor.markdown = '# New Content';html (read-only)
Get the rendered HTML.
const html = editor.html;mode (read-only)
Get the current view mode.
const currentMode = editor.mode; // 'source', 'split', or 'preview'View Modes
Source Mode
Shows only the markdown source editor.
editor.setMode('source');Split Mode
Shows markdown source and preview side-by-side.
editor.setMode('split');Preview Mode
Shows only the rendered preview (still editable).
editor.setMode('preview');Themes
Light Theme
const editor = new QuikdownEditor('#editor', {
theme: 'light'
});Dark Theme
const editor = new QuikdownEditor('#editor', {
theme: 'dark'
});Auto Theme
Follows system preference.
const editor = new QuikdownEditor('#editor', {
theme: 'auto' // Default
});Styling the Editor Preview
The editor ships with intentionally plain preview styles — black text (or
light text in dark mode), browser-default heading sizes, no decorative
borders. This means it blends into any parent page without clashing.
To customise the preview, add CSS rules that target .qde-preview in your
own stylesheet. The editor's built-in rules have normal specificity so a
single-class selector in a <style> tag or external sheet is enough to
override them — no !important needed.
/* Example: branded headings with an accent underline */
.qde-preview h1 {
color: #6c47b8;
border-bottom: 2px solid #e0d4f5;
padding-bottom: 0.3em;
}
/* Tighter paragraph spacing */
.qde-preview p {
margin: 0.25em 0;
}
/* Custom code block look */
.qde-preview pre {
background: #1e1e2e;
color: #cdd6f4;
border-radius: 8px;
padding: 1em;
}If the editor is embedded on a page whose own CSS targets bare element
selectors (h1, p, pre, etc.), those rules will bleed into the
preview. Either scope your page styles (.my-page h1 { ... }) or add
reset overrides on .qde-preview elements.
Headless and Minimal UI Usage
Headless Mode (No UI)
You can use QuikdownEditor programmatically without any UI:
// Create editor without toolbar
const editor = new QuikdownEditor('#hidden-container', {
showToolbar: false,
mode: 'source' // Use source mode for direct text manipulation
});
// Programmatically set content
editor.setMarkdown('# My Document');
// Get rendered HTML
const html = editor.getHTML();
// Convert and transform markdown
const processedMarkdown = editor.getMarkdown();Minimal UI
For a minimal interface with just essential buttons:
const editor = new QuikdownEditor('#editor', {
showToolbar: true,
showRemoveHR: false, // Hide special buttons
mode: 'split',
theme: 'light'
});Custom Toolbar
You can hide the built-in toolbar and create your own:
// Create editor without toolbar
const editor = new QuikdownEditor('#editor', {
showToolbar: false
});
// Add custom buttons
document.getElementById('my-preview-btn').onclick = () => {
editor.setMode('preview');
};
document.getElementById('my-copy-btn').onclick = () => {
editor.copyRendered();
};Plugin Integration
Built-in Fence Types (Lazy Loaded)
The editor includes built-in handlers for common fence types that automatically load required libraries:
| Fence Type | Description | Library | Auto-Loaded |
|---|---|---|---|
svg |
Inline SVG rendering | None | N/A |
html |
Safe HTML rendering | DOMPurify | Yes |
math, katex, tex, latex |
Math equations | MathJax v3 | Yes |
csv |
Comma-separated values table | None | N/A |
psv |
Pipe-separated values table | None | N/A |
tsv |
Tab-separated values table | None | N/A |
json, json5 |
Syntax highlighted JSON | None/Highlight.js | Optional |
geojson |
Interactive maps from GeoJSON | Leaflet | Yes |
stl |
3D model viewer | Three.js | Yes |
mermaid |
Diagrams | Mermaid | Yes |
Highlight.js
Enable syntax highlighting for code blocks:
const editor = new QuikdownEditor('#editor', {
plugins: {
highlightjs: true
}
});The editor will automatically load Highlight.js from CDN if not already present.
Mermaid
Enable Mermaid diagram rendering:
const editor = new QuikdownEditor('#editor', {
plugins: {
mermaid: true
}
});Use in markdown:
```mermaid
graph LR
A[Start] --> B[Process]
B --> C[End]
### Built-in Fence Examples
#### SVG Rendering
````markdown
```svg
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="blue"/>
</svg>
#### Safe HTML (with DOMPurify)
````markdown
```html
<div style="color: blue;">
<h3>Safe HTML</h3>
<p>Scripts and event handlers are removed</p>
</div>
#### Math Equations (KaTeX)
````markdown
```math
E = mc^2\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}
#### Data Tables
````markdown
```csv
Name,Age,City
Alice,30,New York
Bob,25,LondonName|Age|City
Alice|30|New York
Bob|25|London
#### JSON with Syntax Highlighting
````markdown
```json
{
"name": "QuikDown",
"version": "1.0.5",
"features": ["markdown", "preview", "plugins"]
}
### Custom Fence Plugins
Provide custom handlers for specific fence languages using the `customFences` option:
```javascript
const editor = new QuikdownEditor('#editor', {
customFences: {
// Each key is a language identifier
'custom': (code, language) => {
return `<div class="custom">${code}</div>`;
},
'math': (code, language) => {
// Handle math blocks
return `<div class="math">$${code}$</div>`;
}
}
});MathJax Integration
const editor = new QuikdownEditor('#editor', {
customFences: {
'math': (code, lang) => {
if (window.MathJax) {
const id = `math-${Date.now()}`;
const html = `<div id="${id}" class="math-block">\\[${code}\\]</div>`;
// Tell MathJax to render after DOM insertion
setTimeout(() => {
MathJax.typesetPromise([document.getElementById(id)]);
}, 0);
return html;
}
return `<pre class="math-source">${code}</pre>`;
}
}
});KaTeX Integration
const editor = new QuikdownEditor('#editor', {
customFences: {
'math': (code, lang) => {
if (window.katex) {
try {
return katex.renderToString(code, {
displayMode: true,
throwOnError: false
});
} catch (err) {
return `<div class="math-error">Error: ${err.message}</div>`;
}
}
return `<pre>${code}</pre>`;
}
}
});Multiple Custom Plugins
const editor = new QuikdownEditor('#editor', {
customFences: {
// Math rendering
'math': (code, lang) => {
// Your math rendering logic
},
// PlantUML diagrams
'plantuml': (code, lang) => {
const encoded = plantumlEncoder.encode(code);
return `<img src="http://www.plantuml.com/plantuml/svg/${encoded}" alt="PlantUML">`;
},
// JSON visualization
'json': (code, lang) => {
try {
const data = JSON.parse(code);
return `<pre class="json">${JSON.stringify(data, null, 2)}</pre>`;
} catch (err) {
return `<pre class="error">Invalid JSON: ${err.message}</pre>`;
}
},
// Chart rendering
'chart': (code, lang) => {
try {
const config = JSON.parse(code);
return renderChart(config); // Your chart rendering function
} catch (err) {
return `<pre>Invalid chart config: ${err.message}</pre>`;
}
}
},
// You can still use built-in plugins alongside custom ones
plugins: {
highlightjs: true, // For other code blocks
mermaid: true // Built-in mermaid support
}
});Note: Custom fences are checked first, before built-in plugins. If a custom fence returns undefined, it falls back to built-in behavior.
Events and Callbacks
onChange
Called when content changes:
const editor = new QuikdownEditor('#editor', {
onChange: (markdown, html) => {
console.log('Markdown:', markdown);
console.log('HTML:', html);
}
});onModeChange
Called when view mode changes:
const editor = new QuikdownEditor('#editor', {
onModeChange: (mode) => {
console.log('Mode changed to:', mode);
}
});Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Ctrl/Cmd + Z |
Undo |
Ctrl/Cmd + Shift + Z |
Redo |
Ctrl/Cmd + 1 |
Switch to source mode |
Ctrl/Cmd + 2 |
Switch to split mode |
Ctrl/Cmd + 3 |
Switch to preview mode |
Examples
Complete Example with All Features
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Quikdown Editor - Full Example</title>
</head>
<body>
<div id="editor" style="height: 500px;"></div>
<div id="status"></div>
<script type="module">
import QuikdownEditor from 'https://unpkg.com/quikdown/dist/quikdown_edit.esm.min.js';
const editor = new QuikdownEditor('#editor', {
mode: 'split',
theme: 'auto',
showToolbar: true,
lazy_linefeeds: false,
debounceDelay: 300,
placeholder: 'Start typing markdown...',
initialContent: '# Welcome to Quikdown Editor\n\n' +
'## Features\n\n' +
'- **Bold** and *italic* text\n' +
'- [Links](https://example.com)\n' +
'- `Code blocks`\n\n' +
'```javascript\n' +
'console.log("Hello World");\n' +
'```\n',
plugins: {
highlightjs: true,
mermaid: true
},
onChange: (markdown, html) => {
document.getElementById('status').textContent =
`Characters: ${markdown.length}`;
},
onModeChange: (mode) => {
console.log('Mode changed to:', mode);
}
});
// Programmatic control
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
const content = editor.getMarkdown();
console.log('Saving:', content);
// Save to server or localStorage
}
});
</script>
</body>
</html>Integration with Form
const form = document.getElementById('myForm');
const editor = new QuikdownEditor('#editor');
form.addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(form);
formData.append('content', editor.getMarkdown());
fetch('/api/save', {
method: 'POST',
body: formData
});
});Multiple Editors
const editor1 = new QuikdownEditor('#editor1', {
mode: 'source',
theme: 'light'
});
const editor2 = new QuikdownEditor('#editor2', {
mode: 'preview',
theme: 'dark'
});
// Sync content between editors
editor1.onChange = (markdown) => {
editor2.setMarkdown(markdown);
};With Custom Styling
<style>
#custom-editor {
border: 2px solid #007bff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
#custom-editor .qde-toolbar {
background: linear-gradient(to right, #007bff, #0056b3);
}
#custom-editor .qde-btn {
background: white;
color: #007bff;
}
</style>
<div id="custom-editor" style="height: 400px;"></div>
<script type="module">
import QuikdownEditor from 'https://unpkg.com/quikdown/dist/quikdown_edit.esm.min.js';
const editor = new QuikdownEditor('#custom-editor', {
mode: 'split',
theme: 'light'
});
</script>Complete Example with MathJax
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Quikdown Editor with MathJax</title>
<!-- Load MathJax -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<style>
body { margin: 20px; font-family: Arial, sans-serif; }
#editor { height: 500px; border: 1px solid #ddd; }
.math-block { margin: 1em 0; }
</style>
</head>
<body>
<h1>Quikdown Editor with Math Support</h1>
<div id="editor"></div>
<script type="module">
import QuikdownEditor from 'https://unpkg.com/quikdown/dist/quikdown_edit.esm.min.js';
// Wait for MathJax to load
window.MathJax = {
tex: {
inlineMath: [['§CB59§#39;, '§CB59§#39;], ['\\(', '\\)']],
displayMath: [['$', '$'], ['\\[', '\\]']]
}
};
const editor = new QuikdownEditor('#editor', {
mode: 'split',
showToolbar: true,
customFences: {
// Handle ```math blocks
'math': (code, lang) => {
const id = `math-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// Schedule MathJax rendering
setTimeout(() => {
const element = document.getElementById(id);
if (element && window.MathJax) {
MathJax.typesetPromise([element]).catch(err => {
console.error('MathJax error:', err);
});
}
}, 10);
return `<div id="${id}" class="math-block">\\[${code}\\]</div>`;
},
// Handle ```latex blocks
'latex': (code, lang) => {
const id = `latex-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
setTimeout(() => {
const element = document.getElementById(id);
if (element && window.MathJax) {
MathJax.typesetPromise([element]);
}
}, 10);
return `<div id="${id}" class="latex-block">$${code}$</div>`;
}
},
// Still use highlight.js for other code blocks
plugins: {
highlightjs: true
},
initialContent: `# Math Examples
## Inline Math
This is inline math: $E = mc^2$ (requires inline math processing in your markdown)
## Math Blocks
\`\`\`math
\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}
\`\`\`
## LaTeX Block
\`\`\`latex
\\begin{align}
\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} &= \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\
\\nabla \\cdot \\vec{\\mathbf{E}} &= 4 \\pi \\rho \\\\
\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} &= \\vec{\\mathbf{0}} \\\\
\\nabla \\cdot \\vec{\\mathbf{B}} &= 0
\\end{align}
\`\`\`
## Regular Code Block
\`\`\`javascript
// This still gets syntax highlighted
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
\`\`\`
`
});
</script>
</body>
</html>Browser Support
Quikdown Editor supports all modern browsers:
- Chrome/Edge 88+
- Firefox 78+
- Safari 14+
- iOS Safari 14+
- Chrome for Android 88+
For older browsers, use the UMD build which includes necessary polyfills.
Troubleshooting
Editor not appearing
Ensure the container element has a defined height:
#editor {
height: 400px; /* or min-height */
}Plugins not loading
The editor automatically loads plugins from CDN. If you're behind a firewall or offline, load them manually:
<!-- Load before creating editor -->
<link rel="stylesheet" href="path/to/highlight.css">
<script src="path/to/highlight.js"></script>
<script src="path/to/mermaid.js"></script>Content not syncing
Ensure you're using the bidirectional build of quikdown:
// The editor automatically uses quikdown_bd for bidirectional support
// This is built into quikdown_edit.jsAdvanced Usage
Custom Toolbar Actions
// After editor creation
const toolbar = editor.container.querySelector('.qde-toolbar');
const customBtn = document.createElement('button');
customBtn.className = 'qde-btn';
customBtn.textContent = 'Export';
customBtn.onclick = () => {
const blob = new Blob([editor.getMarkdown()], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.md';
a.click();
};
toolbar.appendChild(customBtn);Programmatic Content Manipulation
// Insert at cursor position
const insertAtCursor = (text) => {
const textarea = editor.sourceTextarea;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
textarea.value = value.substring(0, start) + text + value.substring(end);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
// Trigger update
textarea.dispatchEvent(new Event('input'));
};
// Usage
insertAtCursor('**bold text**');Performance Tips
- Debouncing: Adjust
debounceDelayfor large documents - Lazy Loading: Plugins are loaded on-demand
- Mode Selection: Use source mode for large documents to improve performance
- Memory Management: Call
destroy()when removing editors
License
BSD 2-Clause License - see LICENSE.txt for details.