A modern, extensible markdown editor and previewer for the web.
Installation β’ Quick Start β’ Usage β’ Features β’ API β’ License
Draftly is a powerful, pluggable markdown editor and preview toolkit built on top of CodeMirror 6. It provides a seamless "rich text" editing experience while preserving standard markdown syntax. Draftly also includes a static HTML renderer that produces output visually identical to the editor, making it perfect for blogs, documentation sites, and content management systems.
- π Modern Architecture: Built on CodeMirror 6 with incremental Lezer parsing.
- π¨ Rich Editing: WYSIWYG-like experience with full markdown control.
- π Extensible Plugin System: Add custom rendering, keymaps, and syntax.
- πΌοΈ Static Preview: Render markdown to semantic HTML with visual parity.
- π Theming: First-class support for light and dark modes.
- π¦ Modular Exports: Import only what you need (
draftly/editor,draftly/preview,draftly/plugins).
Install the package via your preferred package manager:
# npm
npm install draftly
# yarn
yarn add draftly
# pnpm
pnpm add draftly
# bun
bun add draftlyDraftly requires the following CodeMirror packages as peer dependencies. Make sure they are installed in your project:
npm install @codemirror/commands @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/viewGet up and running in seconds.
import { EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { draftly } from "draftly";
const view = new EditorView({
state: EditorState.create({
doc: "# Hello, Draftly!",
extensions: [draftly()],
}),
parent: document.getElementById("editor")!,
});Draftly is designed for flexibility. Use it as a CodeMirror extension for interactive editing or as a standalone renderer for static previews.
Here's a complete example using @uiw/react-codemirror:
import CodeMirror from "@uiw/react-codemirror";
import { draftly, allPlugins, ThemeEnum } from "draftly";
import { githubDark } from "@uiw/codemirror-theme-github";
function MarkdownEditor() {
return (
<CodeMirror
value="# Welcome to Draftly\n\nStart writing..."
height="500px"
extensions={[
draftly({
theme: ThemeEnum.DARK,
themeStyle: githubDark,
plugins: allPlugins,
lineWrapping: true,
history: true,
indentWithTab: true,
onNodesChange: (nodes) => console.log("AST:", nodes),
}),
]}
/>
);
}| Option | Type | Default | Description |
|---|---|---|---|
theme |
ThemeEnum |
ThemeEnum.AUTO |
Theme mode: LIGHT, DARK, or AUTO. |
themeStyle |
Extension |
undefined |
CodeMirror theme extension (e.g., githubDark). |
plugins |
DraftlyPlugin[] |
[] |
Plugins to enable for rendering and parsing. |
baseStyles |
boolean |
true |
Load default base styles. |
disableViewPlugin |
boolean |
false |
Disable rich rendering (raw markdown mode). |
defaultKeybindings |
boolean |
true |
Enable default CodeMirror keybindings. |
history |
boolean |
true |
Enable undo/redo history. |
indentWithTab |
boolean |
true |
Use Tab for indentation. |
highlightActiveLine |
boolean |
true |
Highlight the current line (in raw mode). |
lineWrapping |
boolean |
true |
Enable line wrapping. |
onNodesChange |
(nodes: DraftlyNode[]) => void |
undefined |
Callback fired on every document update with parsed AST. |
markdown |
MarkdownConfig[] |
[] |
Additional Lezer markdown parser extensions. |
extensions |
Extension[] |
[] |
Additional CodeMirror extensions. |
keymap |
KeyBinding[] |
[] |
Additional keybindings. |
Render markdown to semantic HTML for server-side rendering, static site generation, or read-only views.
import { preview, generateCSS, allPlugins, ThemeEnum } from "draftly";
const markdown = `
# Hello World
This is a **bold** statement with some \`inline code\`.
- Item 1
- Item 2
- Item 3
`;
// Generate HTML
const html = preview(markdown, {
theme: ThemeEnum.LIGHT,
plugins: allPlugins,
sanitize: true,
wrapperClass: "prose",
});
// Generate matching CSS
const css = generateCSS({
theme: ThemeEnum.LIGHT,
plugins: allPlugins,
wrapperClass: "prose",
includeBase: true,
});
// Use in your app
function ArticlePreview() {
return (
<>
<style>{css}</style>
<article dangerouslySetInnerHTML={{ __html: html }} />
</>
);
}| Option | Type | Default | Description |
|---|---|---|---|
plugins |
DraftlyPlugin[] |
[] |
Plugins for rendering. |
theme |
ThemeEnum |
ThemeEnum.AUTO |
Theme mode. |
sanitize |
boolean |
true |
Sanitize HTML output (via DOMPurify). |
wrapperClass |
string |
"draftly-preview" |
CSS class for the wrapper element. |
wrapperTag |
string |
"article" |
HTML tag for the wrapper element. |
markdown |
MarkdownConfig[] |
[] |
Additional parser extensions. |
Draftly's ViewPlugin decorates the editor to hide markdown syntax and render styled content inline. This provides a WYSIWYG-like experience while keeping the source as plain markdown.
- Inline Formatting: Bold, italic, strikethrough, and code are styled in-place.
- Headings: Rendered with proper sizes and weights.
- Lists: Ordered and unordered lists with custom bullets.
- Images: Displayed inline with alt text and captions.
- Links: Clickable with visual distinction.
- Code Blocks: Syntax highlighted with language detection.
Every feature in Draftly is a plugin. Plugins can provide:
- CodeMirror Extensions: Custom decorations, widgets, and behaviors.
- Markdown Parser Extensions: Extend the Lezer parser for custom syntax.
- Keymaps: Add keyboard shortcuts.
- Themes: Inject custom styles based on the current theme.
- Preview Renderers: Define how elements are rendered to static HTML.
import { DraftlyPlugin } from "draftly/editor";
class MyCustomPlugin extends DraftlyPlugin {
name = "my-custom-plugin";
onRegister(context) {
console.log("Plugin registered!", context.config);
}
getExtensions() {
return [
/* CodeMirror extensions */
];
}
getKeymap() {
return [
/* KeyBinding[] */
];
}
getMarkdownConfig() {
return {
/* MarkdownConfig */
};
}
theme(mode) {
return {
/* Theme spec */
};
}
}Access the parsed document structure via the onNodesChange callback. Perfect for building:
- Table of Contents
- Document Outlines
- Navigation Breadcrumbs
- Word/Line Counters
type DraftlyNode = {
from: number; // Start position
to: number; // End position
name: string; // Node type (e.g., "Heading", "Paragraph")
children: DraftlyNode[];
isSelected: boolean; // True if cursor is within this node
};Draftly provides seamless theming with automatic light/dark mode support:
- Auto Detection: Follows system preference with
ThemeEnum.AUTO. - Manual Control: Force
ThemeEnum.LIGHTorThemeEnum.DARK. - Custom Themes: Pass any CodeMirror theme via
themeStyle. - Preview Parity: CSS generation ensures preview matches editor styling.
Import only what you need to minimize bundle size:
// Full package
import { draftly, preview, allPlugins } from "draftly";
// Editor only
import { draftly, DraftlyPlugin } from "draftly/editor";
// Preview only
import { preview, generateCSS } from "draftly/preview";
// Individual plugins
import { HeadingPlugin, ListPlugin } from "draftly/plugins";| Export | Path | Description |
|---|---|---|
draftly |
draftly/editor |
Main editor extension factory. |
DraftlyPlugin |
draftly/editor |
Base class for creating plugins. |
ThemeEnum |
draftly/editor |
Enum for theme modes (AUTO, LIGHT, DARK). |
DraftlyNode |
draftly/editor |
Type for AST nodes. |
preview |
draftly/preview |
Function to render markdown to HTML. |
generateCSS |
draftly/preview |
Function to generate CSS for preview styling. |
allPlugins |
draftly/plugins |
Array of all built-in plugins. |
Draftly supports all modern browsers:
| Browser | Version |
|---|---|
| Chrome | 88+ |
| Firefox | 78+ |
| Safari | 14+ |
| Edge | 88+ |
Contributions are welcome! Please read our Contributing Guide before submitting a pull request.
MIT Β© NeuroNexul