Typora and Obsidian style live-preview Markdown editing for CodeMirror 6.
Markers (**, *, `, #, >, …) hide when your cursor leaves the line and reappear when it returns. Headings, lists, links, blockquotes, and fenced code highlight in place. The document on disk is still plain Markdown.
silkdown.dev · docs · examples
Most React Markdown editors (Milkdown, Tiptap, MDXEditor) are built on ProseMirror. They store your document as an AST — when you type **bold**, the asterisks are consumed and gone. There is nothing to "reveal" when the cursor returns.
CodeMirror 6 takes the opposite approach: the document is the source string, and decorations sit on top of it. That makes "show markers when the cursor is on this line" a natural fit instead of an architectural fight.
Silkdown is the smallest set of extensions needed to get that experience, packaged so you can drop it into a React, Vue, Svelte, vanilla JS, or Electron app. The core is framework-agnostic; the React adapter is a thin wrapper.
| Package | Version | Description |
|---|---|---|
@silkdown/core |
Framework-agnostic CodeMirror 6 extension. The silkdown() factory. |
|
@silkdown/react |
React wrapper. One controlled component plus an imperative ref. |
pnpm add @silkdown/react @silkdown/core react react-dom \
@codemirror/state @codemirror/view
import { useState } from "react";
import { SilkdownEditor } from "@silkdown/react";
import "@silkdown/core/theme.css";
export function Editor() {
const [value, setValue] = useState("# Hello\n\n**world**");
return <SilkdownEditor value={value} onChange={setValue} />;
}
The component is controlled. value changes are diffed into the editor without recreating it, so cursor position, selection, and undo history survive across re-renders.
import { EditorState } from "@codemirror/state";
import { EditorView, basicSetup } from "codemirror";
import { silkdown } from "@silkdown/core";
import "@silkdown/core/theme.css";
new EditorView({
state: EditorState.create({
doc: "# Hello\n\nWorld with **bold** text.",
extensions: [basicSetup, silkdown()],
}),
parent: document.querySelector("#editor")!,
});
silkdown() does not include basicSetup — compose it yourself so you choose whether to enable line numbers, gutters, bracket matching, and the rest. CodeMirror and Lezer packages are peer dependencies; bundling two copies silently breaks decorations.
http://, https://, data:image/*, relative paths). javascript: and friends are blocked.<hr>.@codemirror/lang-markdown's codeLanguages.Mod-B, Mod-I, Mod-` to toggle inline marks. Enter continues list markers.Pre-1.0 (0.x). The public API is small and approaching stable, but minor versions may still introduce breaking changes. Track them via the changelog and changesets.
The architecture (decoration pipeline, peer-dep boundaries, atomic-range cursor motion) is settled — see docs/architecture.md. The known footgun list when extending Silkdown lives in docs/gotchas.md.
The full docs and a live playground live at silkdown.dev:
In-repo references for working on the source:
urlPolicy, fenced-code config.SilkdownHandle, SSR notes.atomicRanges cooperate to drive Live Preview.corepack enable
corepack [email protected] install
pnpm dev # demo at http://localhost:5173
pnpm test # unit + integration across packages
pnpm test:e2e # Playwright suite
pnpm verify # typecheck + lint + format + tests + build + size budget
Node 22+ is required.
Pull requests are welcome. Read CONTRIBUTING.md first — it covers the development loop, the public-API gate (any change to packages/*/src/index.ts must be mirrored in packages/api-snapshot/src/consumer.ts), and the changeset workflow.
By contributing you agree to abide by the Code of Conduct.
Please do not open public issues for security findings. See SECURITY.md for the disclosure process.
Silkdown stands on a lot of prior art:
@lezer/markdown — the incremental Markdown parser.codemirror-rich-markdoc — closest existing implementation; a useful reference.MIT © Martin Garcia