🌐
HTML Output
Convert Markdown to clean HTML with optional heading IDs, custom CSS class injection per element, GFM tables, task lists, and built-in URL sanitisation.
🖥
ANSI Terminal
Render Markdown in the terminal with ANSI escape codes. Fully themeable without chalk, with word-wrap, box-drawing tables, and OSC 8 hyperlink support.
🌲
Doc Tree
Parse Markdown into a typed JSON tree. Perfect for ProseMirror, Slate, Quill, and other rich-text editors. Flat inline marks — no nested AST wrangling.
See It In Action
- toHtml()
- toAnsi()
- toDocTree()
- Custom Serializer
import { toHtml } from 'md-to-rich'
const html = toHtml(`
# Welcome
A paragraph with **bold**, *italic*, and \`inline code\`.
> A blockquote
- Item one
- Item two
`, { headingIds: true, classNames: { p: 'prose' } })
// Output:
// <h1 id="welcome">Welcome</h1>
// <p class="prose">A paragraph with <strong>bold</strong>,
// <em>italic</em>, and <code>inline code</code>.</p>
// <blockquote><p>A blockquote</p></blockquote>
// <ul><li>Item one</li><li>Item two</li></ul>
import { toAnsi } from 'md-to-rich'
const output = toAnsi(`
# Hello World
A paragraph with **bold** and *italic* text.
\`\`\`typescript
const x: number = 42
\`\`\`
`, { columns: 80, hyperlinks: false })
// Output: ANSI-escaped string rendered in terminal
// - h1 rendered bold + underlined
// - Bold/italic inline styles
// - Code block with box-drawing border
import { toDocTree } from 'md-to-rich'
import type { DocDocument } from 'md-to-rich'
const tree: DocDocument = toDocTree('# Hello\n\nWorld')
// Output:
// {
// type: 'document',
// children: [
// { type: 'heading', depth: 1,
// children: [{ type: 'text', value: 'Hello',
// bold: false, italic: false, strikethrough: false }] },
// { type: 'paragraph',
// children: [{ type: 'text', value: 'World',
// bold: false, italic: false, strikethrough: false }] }
// ]
// }
import type { Root } from 'mdast'
import { serialize } from 'md-to-rich'
import type { Serializer } from 'md-to-rich'
const PlainText: Serializer<string> = {
serialize(ast: Root): string {
function extract(node: any): string {
if (node.value !== undefined) return node.value
if (node.children) return node.children.map(extract).join('')
return ''
}
return extract(ast)
},
}
const result = serialize('# Hello\n\nThis is **bold** text.', PlainText)
// → 'HelloThis is bold text.'
Extensible by Design
The Serializer<T> interface is the only contract. Implement it to add any output format — plain text, Word count, PDF metadata — without touching this package. No inheritance, no registration, no boilerplate.
export interface Serializer<TOutput, TOptions = Record<never, never>> {
serialize(ast: Root, options: TOptions): TOutput
}