CodeMirror 6
The recommended markdown editor engine. Modular, performant, and the foundation of Obsidian's editor.
Editing Modes
Use the toggle above to switch between three modes:
- Source — Raw markdown with syntax highlighting and live preview decorations
- Split — Side-by-side source and rendered preview
- Preview — Rendered HTML only
Live Preview Decorations (Obsidian-Style)
The key innovation: markdown syntax markers (**, *, ~~, #) are visually hidden when your cursor is on a different line, replaced with rendered formatting. Move your cursor to any line to reveal its raw markdown.
import { ViewPlugin, Decoration, WidgetType } from '@codemirror/view';
// Obsidian-style live preview: hide markdown syntax
// when cursor is NOT on the current line
const livePreviewPlugin = ViewPlugin.fromClass(class {
decorations;
constructor(view) {
this.decorations = this.buildDecorations(view);
}
update(update) {
if (update.docChanged || update.selectionSet) {
this.decorations = this.buildDecorations(update.view);
}
}
buildDecorations(view) {
const widgets = [];
const cursorLine = view.state.doc.lineAt(
view.state.selection.main.head
).number;
// For each line NOT containing the cursor:
// - Find markdown syntax (**, *, ~~, #, etc.)
// - Replace markers with Decoration.mark({ class: 'hidden' })
// - Style the content (bold, italic, heading size, etc.)
return Decoration.set(widgets);
}
}, { decorations: v => v.decorations });Custom Extensions
Everything in CM6 is an extension. Here's a word counter as a StateField:
import { StateField } from '@codemirror/state';
// Custom state field: word counter
const wordCountField = StateField.define<number>({
create(state) {
return countWords(state.doc.toString());
},
update(value, tr) {
if (tr.docChanged) {
return countWords(tr.state.doc.toString());
}
return value;
}
});
// Use it: state.field(wordCountField) → numberExtension Types
State Fields
Custom state attached to the editor (e.g., word count, decoration state, fold state)
View Plugins
DOM-interacting logic (e.g., tooltip positioning, live preview decorations)
Facets
Configurable behaviors (e.g., tab size, line wrapping, read-only mode)
Keymaps
Key binding configurations. Compose multiple keymaps with priority ordering.
Decorations
Visual modifications without changing the document (marks, widgets, line decorations)
Themes
CSS-in-JS theming via EditorView.theme() — composable and overridable
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Undo | Ctrl+Z |
| Redo | Ctrl+Shift+Z |
| Select all | Ctrl+A |
| Indent | Tab |
| Move line up | Alt+↑ |
| Move line down | Alt+↓ |
| Toggle comment | Ctrl+/ |
Strengths
- Modular — import only what you need (~150KB)
- Excellent performance (virtual rendering)
- First-class TypeScript support
- Mobile & accessibility support
- Markdown-native with Lezer parser
- Powers Obsidian — proven at scale
- Active development by Marijn Haverbeke
Weaknesses
- Steeper learning curve than simpler editors
- No built-in WYSIWYG — requires decorations
- Documentation is thorough but dense
- No official Svelte wrapper (easy to make)