Samples
Each sample shows its Markdown next to the output, rendered when this site was built by the same library you would install. In your browser, KaTeX typesets the math and Mermaid draws the diagrams, and the last sample is a live editor; everything else is static HTML.
Podcast rundown: a complete custom-plugin sample →
A split editor with participant completion, planned durations and separate supporting documents. Implementation guide.
CommonMark basics
Headings, emphasis, links, nested lists, block quotes, code, entities and escapes: the core alone, no plugins. Raw HTML stays text because html is off by default.
Configuration: createMarkdown()
Markdown
# A first-level heading
A paragraph with *emphasis*, **strong emphasis**, `inline code` and a
[link with a title](https://commonmark.org "CommonMark"). Adjacent lines
make one paragraph; two trailing spaces
make a hard line break.
## Lists
1. Ordered lists
2. may contain
- bullet lists,
- nested
- A [reference link][spec]
- An autolink: <https://spec.commonmark.org/0.31.2/>
- Entities: © — ½
- Escapes: \*not emphasis\*
> A block quote can hold **any** block.
>
> ```js
> const md = createMarkdown()
> ```
Code indented by four spaces.
***
<b>Raw HTML</b> stays text: the `html` option is off by default.
[spec]: https://spec.commonmark.org "The CommonMark spec"
Result
A first-level heading
A paragraph with emphasis, strong emphasis, inline code and a
link with a title. Adjacent lines
make one paragraph; two trailing spaces
make a hard line break.
Lists
- Ordered lists
- may contain
- bullet lists,
- nested
- A reference link
- An autolink: https://spec.commonmark.org/0.31.2/
- Entities: © — ½
- Escapes: *not emphasis*
A block quote can hold any block.
const md = createMarkdown()
Code indented by four spaces.
<b>Raw HTML</b> stays text: the html option is off by default.
GitHub Flavored Markdown
Aligned tables, task lists, strikethrough, extended autolinks, footnotes and GitHub alerts, with gfm().
Configuration: createMarkdown({ plugins: gfm() })
Markdown
## Tables
| Syntax | Markdown | Result |
|:--|:--:|--:|
| Emphasis | `*a*` | *a* |
| Strong | `**a**` | **a** |
| Strikethrough | `~~a~~` | ~~a~~ |
## Task lists
- [x] CommonMark 0.31.2
- [x] GFM extensions
- [ ] Your plugin
## Strikethrough and autolinks
The ~~old~~ new renderer. Links without brackets: www.commonmark.org,
https://github.github.com/gfm/ and an address like info@example.com.
## Footnotes
Parsing stays linear on adversarial input.[^adversarial]
[^adversarial]: Checked by `test/pathological.test.ts` in the repository.
## Alerts
> [!NOTE]
> Alerts are blockquotes whose first line is `[!KIND]`.
> [!TIP]
> Neutral colors derive from `currentColor`.
> [!WARNING]
> `tagfilter` is not a sanitizer.
Result
Tables
| Syntax | Markdown | Result |
|---|---|---|
| Emphasis | *a* |
a |
| Strong | **a** |
a |
| Strikethrough | ~~a~~ |
Task lists
- CommonMark 0.31.2
- GFM extensions
- Your plugin
Strikethrough and autolinks
The old new renderer. Links without brackets: www.commonmark.org,
https://github.github.com/gfm/ and an address like info@example.com.
Footnotes
Parsing stays linear on adversarial input.1
Alerts
Note
Alerts are blockquotes whose first line is [!KIND].
Tip
Neutral colors derive from currentColor.
Warning
tagfilter is not a sanitizer.
-
Checked by
test/pathological.test.tsin the repository. ↩
Math with KaTeX
The math plugin outputs \(…\) and \[…\], the default delimiters of KaTeX auto-render: here KaTeX typesets the formulas in the browser with no configuration.
Configuration: createMarkdown({ plugins: [math()] })
Markdown
Euler's identity, $e^{i\pi} + 1 = 0$, sits in a line of text.
Double dollars make display math: $$\sum_{k=1}^{n} k = \frac{n(n+1)}{2}$$
$$
\int_0^1 x^2 \, dx = \frac{1}{3}
$$
$$
\begin{pmatrix} a & b \\ c & d \end{pmatrix}
\begin{pmatrix} x \\ y \end{pmatrix}
=
\begin{pmatrix} ax + by \\ cx + dy \end{pmatrix}
$$
Prices like $5 and $6 stay text: a dollar sign after a space never
closes the math.
Result
Euler's identity, \(e^{i\pi} + 1 = 0\), sits in a line of text. Double dollars make display math: \[\sum_{k=1}^{n} k = \frac{n(n+1)}{2}\]
Prices like $5 and $6 stay text: a dollar sign after a space never closes the math.
Mermaid diagrams
The mermaid plugin turns ```mermaid fences into <pre class="mermaid">. Here renderMermaid() loads Mermaid only because this page has diagrams, and themes them from the --md-* tokens: switch the color scheme and they are redrawn. Without JavaScript you would see the source.
Configuration: createMarkdown({ plugins: [mermaid()] })
Markdown
How mdlib-pure turns Markdown into HTML:
```mermaid
flowchart TD
src[Markdown] --> parse[Block and inline parsers]
parse --> ast[(AST)]
ast -->|plugins| transforms{{Transforms}}
transforms --> vnodes[VNodes]
vnodes --> html[HTML string]
vnodes --> dom[DOM nodes]
```
And who calls whom when a page renders a diagram:
```mermaid
sequenceDiagram
participant Page
participant mdlib as mdlib-pure
participant Mermaid
Page->>mdlib: renderTo(el, markdown)
mdlib-->>Page: <pre class="mermaid">
Page->>mdlib: renderMermaid(el, { load })
mdlib->>Mermaid: load(), only if there are diagrams
Mermaid-->>Page: SVG, colored from the tokens
```
Result
How mdlib-pure turns Markdown into HTML:
flowchart TD
src[Markdown] --> parse[Block and inline parsers]
parse --> ast[(AST)]
ast -->|plugins| transforms{{Transforms}}
transforms --> vnodes[VNodes]
vnodes --> html[HTML string]
vnodes --> dom[DOM nodes]
And who calls whom when a page renders a diagram:
sequenceDiagram
participant Page
participant mdlib as mdlib-pure
participant Mermaid
Page->>mdlib: renderTo(el, markdown)
mdlib-->>Page: <pre class="mermaid">
Page->>mdlib: renderMermaid(el, { load })
mdlib->>Mermaid: load(), only if there are diagrams
Mermaid-->>Page: SVG, colored from the tokens
Definition lists
Terms and definitions, Pandoc and PHP Markdown Extra style, rendered as <dl>, <dt> and <dd>.
Configuration: createMarkdown({ plugins: [deflist()] })
Markdown
CommonMark
: The reference specification of Markdown syntax.
GFM
: GitHub Flavored Markdown: CommonMark plus tables, strikethrough,
autolinks, task lists, footnotes and alerts.
Parser
Renderer
: Two names, two phases of the same job.
A second paragraph, indented to the content column.
Result
- CommonMark
-
The reference specification of Markdown syntax.
- GFM
-
GitHub Flavored Markdown: CommonMark plus tables, strikethrough, autolinks, task lists, footnotes and alerts.
- Parser
- Renderer
-
Two names, two phases of the same job.
A second paragraph, indented to the content column.
Containers
Fenced ::: blocks with a kind and an optional title, nestable with longer fences. The kind becomes the class md-container-KIND; the look is up to the site.
Configuration: createMarkdown({ plugins: [container()] })
Markdown
::: note
A container without a title takes any block, **even** lists:
- one
- two
:::
::: warning Before you publish
Run `npm run verify`.
:::
:::: details Nested containers
To nest, use more colons on the outside.
::: tip
A container inside another one.
:::
::::
Result
A container without a title takes any block, even lists:
- one
- two
Before you publish
Run npm run verify.
Nested containers
To nest, use more colons on the outside.
A container inside another one.
Highlighted text
==text== becomes <mark>, with the flanking rules of emphasis. A == line under a paragraph is still a setext heading.
Configuration: createMarkdown({ plugins: [mark()] })
Markdown
==Highlighted== text becomes `<mark>`, with the same rules as
emphasis: it nests in **==strong==** and *==emphasis==*, and works
inside wo==r==ds too.
These stay text: =one=, ===three===, and == with spaces ==.
A setext heading
==
Result
Highlighted text becomes <mark>, with the same rules as
emphasis: it nests in strong and emphasis, and works
inside words too.
These stay text: =one=, ===three===, and == with spaces ==.
A setext heading
Front matter
YAML or TOML metadata at the top of a document: kept out of the output, readable with getFrontmatter().
Configuration: createMarkdown({ plugins: [frontmatter()] })
Markdown
---
title: Release notes
date: 2026-10-28
tags: [markdown, commonmark]
---
# Front matter is not rendered
The YAML block at the top stays out of the HTML: `getFrontmatter(root)`
returns its raw text, with no YAML parser (zero dependencies).
Result
Front matter is not rendered
The YAML block at the top stays out of the HTML: getFrontmatter(root)
returns its raw text, with no YAML parser (zero dependencies).
getFrontmatter(md.parse(source))
{
"type": "yaml",
"value": "title: Release notes\ndate: 2026-10-28\ntags: [markdown, commonmark]"
}
Syntax highlighting hook
The highlight(code, lang, meta) option takes any highlighter. This site uses a tokenizer of a few lines that returns VNodes, so the output stays on the safe path of renderTo().
Configuration
import { createMarkdown, h } from 'mdlib-pure'
import { grammars, tokenize } from './highlight.js' // ~60 lines, site/lib/highlight.js
const md = createMarkdown({
// VNodes keep renderTo() on its DOM-safe path; null means default rendering
highlight: (code, lang) =>
grammars.has(lang)
? tokenize(code, grammars.get(lang)).map(([cls, text]) => (cls ? h('span', { class: `tok-${cls}` }, text) : text))
: null,
})
Markdown
```js
// A minimal highlighter: returns VNodes, not HTML
const md = createMarkdown({
highlight: (code, lang) => (lang === 'js' ? tokens(code) : null),
})
md.render('# Hello')
```
```css
:root {
--md-color-link: oklch(55% 0.2 260);
}
```
```sh
npm install mdlib-pure
```
Result
// A minimal highlighter: returns VNodes, not HTML
const md = createMarkdown({
highlight: (code, lang) => (lang === 'js' ? tokens(code) : null),
})
md.render('# Hello')
:root {
--md-color-link: oklch(55% 0.2 260);
}
npm install mdlib-pure
Tailwind classes map
Without the theme, the classes option gives each node type its utility classes. The preview uses a real Tailwind CSS v4 build, isolated in a shadow root.
Configuration
createMarkdown({
classes: {
heading: 'font-semibold tracking-tight',
heading2: 'mb-3 text-2xl',
paragraph: 'my-3 leading-7',
link: 'font-medium text-(--md-color-link) underline decoration-2 underline-offset-4',
inlineCode: 'rounded bg-current/10 px-1 font-mono text-sm',
list: 'my-3 list-disc pl-6 marker:text-(--md-color-link)',
blockquote: 'my-3 border-l-4 border-(--md-color-link) pl-4 italic',
},
})
Markdown
## A heading made of utilities
Every node type gets its own classes: no theme, just **Tailwind**,
[links](https://tailwindcss.com) and `inline code` included.
- first item
- second item
> A block quote with a left border.
Result (Tailwind CSS v4 utilities, no theme)
A heading made of utilities
Every node type gets its own classes: no theme, just Tailwind,
links and inline code included.
- first item
- second item
A block quote with a left border.
React adapter
mdlib-pure/adapter turns VNodes into React, Preact or Vue elements. This preview was produced by react-dom/server when the site was built.
Configuration
import { createElement } from 'react'
import { createMarkdown } from 'mdlib-pure'
import { gfm } from 'mdlib-pure/gfm'
import { toElements } from 'mdlib-pure/adapter'
const md = createMarkdown({ plugins: gfm() })
export function Markdown({ source }) {
return <article className="md">{toElements(md.toVNodes(source), createElement, 'react')}</article>
}
Markdown
## From Markdown to React
`toElements()` turns VNodes into React elements with `createElement`.
- [x] `className` instead of `class`
- [x] uncontrolled checkboxes (`defaultChecked`)
- [x] a `key` on top-level elements
Result (react-dom/server)
From Markdown to React
toElements() turns VNodes into React elements with createElement.
-
classNameinstead ofclass - uncontrolled checkboxes (
defaultChecked) - a
keyon top-level elements
Web Components, live
mdlib-pure/element defines <mdlib-markdown> and <mdlib-editor>. Both are live below, defined in your browser from the HTML above: the viewer renders its source attribute, the editor renders its preview on every keystroke and sends a debounced input event. KaTeX and Mermaid come from this site, loaded only when the text needs them.
Configuration
import { defineElements } from 'mdlib-pure/element'
// This site's own KaTeX and Mermaid, fetched only on first use
// (simplified from site/client/elements.js)
defineElements({
loaders: {
katex: () => loadKatexAutoRender(), // resolves to renderMathInElement
katexStylesheet: '/assets/katex/katex.min.css',
mermaid: () => import('/assets/mermaid/mermaid.js'),
},
})
// Debounced: 200 ms after the last keystroke (the delay attribute)
document.querySelector('mdlib-editor').addEventListener('input', (e) => save(e.target.value))
HTML
<mdlib-markdown class="md" plugins="gfm math"
source="**Rendered in your browser** from the `source` attribute,
with GFM and math: ~~strikethrough~~, www.commonmark.org
and $a^2 + b^2 = c^2$."></mdlib-markdown>
<mdlib-editor plugins="all" label="Sample document" style="block-size: 34rem"
value="## Edit me
This is a real `<mdlib-editor>`: the preview renders **on every keystroke**, in your browser.
Select a word and press Ctrl+B, or use the toolbar; *Write* and *Preview* switch the view.
- [x] every plugin, with `plugins` set to `all`
- [ ] your own text
KaTeX is loaded on first use: $e^{i\pi} + 1 = 0$.
::: tip Diagrams
Mermaid loads only when a diagram appears, and a diagram is drawn again only when its source changes.
:::
```mermaid
flowchart LR
type[You type] --> preview[Live preview]
preview -->|input event, debounced| app[Your app]
```
"></mdlib-editor>
Result (live Web Components)
input events from the editor: none yet
Web Components guideThe Blazor wrapperThe Angular wrapperSource of this sample