A minimal, dependency-free markdown editor as a vanilla JavaScript ES6 module.
- Vanilla JavaScript module, zero dependencies
- Syntax highlighting for headings, bold, italic, strikethrough, code, lists, links, images, blockquotes, HTML tags, horizontal rules, front matter and more
- Modular toolbar built from composable tools
- Word wrap toggle with persistent state (localStorage)
- List mode: Tab/Shift-Tab to indent/outdent, auto-continuation on Enter
- Bold with Ctrl/Cmd+B, italic with Ctrl/Cmd+I (provided by tools)
- Native undo/redo support (Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z)
- Lightweight, fast, easy to use
npm install cm-md-editor<textarea id="editor"></textarea>
<script type="module">
import {MdEditor} from "cm-md-editor/src/MdEditor.js"
const editor = new MdEditor(document.getElementById("editor"))
</script>This creates an editor with the default toolbar: Headings (h1–h3), Bold, Italic, Strikethrough, Unordered List, Ordered List, Insert Link, Insert Image.
Compose your own toolbar by passing a tools array:
import {MdEditor} from "cm-md-editor/src/MdEditor.js"
import {Headings} from "cm-md-editor/src/tools/Headings.js"
import {Bold} from "cm-md-editor/src/tools/Bold.js"
import {Italic} from "cm-md-editor/src/tools/Italic.js"
import {Separator} from "cm-md-editor/src/tools/Separator.js"
import {InsertLink} from "cm-md-editor/src/tools/InsertLink.js"
new MdEditor(document.getElementById("editor"), {
tools: [Headings, Separator, Bold, Italic, Separator, InsertLink]
})Tools that accept options can be passed as [ToolClass, props] tuples:
new MdEditor(document.getElementById("editor"), {
tools: [[Headings, {minLevel: 2, maxLevel: 4}], Bold, Italic]
})All props are optional. Pass them as the second argument to the constructor.
| Prop | Type | Default | Description |
|---|---|---|---|
tools |
array |
defaultTools |
Array of tool classes (or [class, props] tuples). See Tools |
wordWrap |
boolean |
true |
Default word wrap state. Overridden by localStorage if the user has toggled it |
colorHeading |
string |
"100,160,255" |
RGB color for headings |
colorCode |
string |
"130,170,200" |
RGB color for code spans and fenced code blocks |
colorComment |
string |
"128,128,128" |
RGB color for HTML comments |
colorLink |
string |
"100,180,220" |
RGB color for links and images |
colorBlockquote |
string |
"100,200,150" |
RGB color for blockquote prefixes |
colorList |
string |
"100,200,150" |
RGB color for list markers |
colorStrikethrough |
string |
"255,100,100" |
RGB color for |
colorBold |
string |
"255,180,80" |
RGB color for bold |
colorItalic |
string |
"180,130,255" |
RGB color for italic |
colorHtmlTag |
string |
"200,120,120" |
RGB color for HTML tags |
colorHorizontalRule |
string |
"128,128,200" |
RGB color for horizontal rules |
colorEscape |
string |
"128,128,128" |
RGB color for escape sequences |
colorFrontMatter |
string |
"128,128,200" |
RGB color for YAML front matter |
Colors are specified as RGB strings (e.g. "255,180,80") and rendered at full opacity.
The toolbar is built entirely from tools. Each tool is a class that provides toolbar buttons, keyboard shortcuts, and/or syntax highlighting extensions.
| Tool | Buttons | Shortcut | Description |
|---|---|---|---|
Headings |
h1, h2, h3 | — | Toggle heading levels. Props: {minLevel, maxLevel} (defaults: 1–3) |
Bold |
bold | Ctrl/Cmd+B | Toggle bold (**) |
Italic |
italic | Ctrl/Cmd+I | Toggle italic (_) |
Strikethrough |
strikethrough | — | Toggle strikethrough (~~) |
UnorderedList |
ul | — | Toggle unordered list prefix (- ) |
OrderedList |
ol | — | Toggle ordered list prefix (1. ) |
InsertLink |
link | — | Insert markdown link |
InsertImage |
image | — | Insert markdown image |
Separator |
— | — | Visual divider in the toolbar. Can be used multiple times |
All built-in tools are exported from src/tools/DefaultTools.js:
import {defaultTools} from "cm-md-editor/src/tools/DefaultTools.js"The default toolbar order is:
Headings | Bold, Italic, Strikethrough | UnorderedList, OrderedList | InsertLink, InsertImage
A tool is a class that receives the editor instance (and optional props) in its constructor. It can implement any combination of three optional methods:
export class MyTool {
constructor(editor, props = {}) {
this.editor = editor
}
// Optional: add buttons to the toolbar
toolbarButtons() {
return [{
name: 'mytool',
title: 'My Tool',
// Icon options (use one):
icon: '<path d="..."/>', // inline SVG path for a 16x16 viewBox
iconFile: 'my-icon.svg', // filename in src/tools/icons/
iconUrl: 'https://...', // full URL to an SVG file
action: () => { /* ... */ }
}]
}
// Optional: register keyboard shortcuts
keyboardShortcuts() {
return [{
key: 'e', // the key to match (KeyboardEvent.key)
ctrlOrMeta: true, // require Ctrl (Windows/Linux) or Cmd (Mac)
action: (e) => { /* ... */ }
}]
}
// Optional: extend syntax highlighting (receives already-escaped HTML)
highlightInline(html) {
return html.replace(...)
}
}For tools with co-located icons, use import.meta.url to resolve the icon path:
toolbarButtons() {
return [{
name: 'mytool',
title: 'My Tool',
iconUrl: new URL("my-icon.svg", import.meta.url).href,
action: () => { /* ... */ }
}]
}These public methods and properties are available via this.editor:
| Method / Property | Description |
|---|---|
editor.element |
The textarea element (read value, selectionStart, selectionEnd) |
editor.insertTextAtCursor(text) |
Insert text at cursor, preserving native undo/redo |
editor.getCurrentLineInfo() |
Returns {lineStart, lineEnd, line} for the current line |
editor.selectLineRange(start, end) |
Set the textarea selection range |
editor.toggleWrap(marker) |
Toggle wrapping markers around the selection (e.g. ** for bold) |
editor.escapeHtml(str) |
Escape a string for use in the highlight layer |
editor.colorSpan(colorProp, content) |
Wrap content in a colored <span> using an RGB color prop |
A tool that inserts lorem ipsum text (see example-addon-tools/DummyText.js):
import {MdEditor} from "cm-md-editor/src/MdEditor.js"
import {defaultTools} from "cm-md-editor/src/tools/DefaultTools.js"
import {Separator} from "cm-md-editor/src/tools/Separator.js"
import {DummyText} from "./example-addon-tools/DummyText.js"
new MdEditor(document.getElementById("editor"), {
tools: [...defaultTools, Separator, DummyText]
})| Shortcut | Action | Provided by |
|---|---|---|
| Ctrl/Cmd + B | Toggle bold | Bold tool |
| Ctrl/Cmd + I | Toggle italic | Italic tool |
| Tab | Indent list item or insert tab | Core editor |
| Shift + Tab | Outdent list item | Core editor |
| Enter | Auto-continue list (unordered and ordered) | Core editor |
MIT
