Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Paragraphs

This is a paragraph of text. Markdown is the source of truth in mkdnsite — browsers see rendered HTML, AI agents see raw Markdown. The same URL serves both formats via content negotiation.

This is a second paragraph. It has bold text, italic text, bold and italic text, and strikethrough text. You can also write inline code within a paragraph.

Images

mkdnsite logo

Blockquotes

This is a blockquote. It can contain formatted text and links.

Blockquotes can also span multiple lines like this.

Nested blockquotes:

This is a nested blockquote. It goes one level deeper.

Unordered Lists

  • First item
  • Second item
    • Nested item A
    • Nested item B
      • Deeply nested item
  • Third item

Ordered Lists

  1. First step
  2. Second step
    1. Sub-step one
    2. Sub-step two
  3. Third step

Ordered list with a custom start:

  1. Fifth item
  2. Sixth item
  3. Seventh item

Task Lists (GFM)

  • Set up the project
  • Add content negotiation
  • Improve default styling
  • Add client-side search

Code Blocks

Inline code: use the createHandler() function to create a fetch handler.

JavaScript

import { createHandler, resolveConfig } from 'mkdnsite'
import { LocalAdapter } from 'mkdnsite/adapters/local'

const config = resolveConfig({
  site: { title: 'My Docs' },
  theme: { mode: 'prose' }
})

const adapter = new LocalAdapter()
const source = adapter.createContentSource(config)
const renderer = await adapter.createRenderer(config)
const handler = createHandler({ source, renderer, config })

TypeScript

interface ContentSource {
  getPage: (slug: string) => Promise<ContentPage | null>
  getNavTree: () => Promise<NavNode>
  listPages: () => Promise<ContentPage[]>
  refresh: () => Promise<void>
}

Bash

# Install mkdnsite globally
bun add -g mkdnsite

# Start a dev server with watch mode
mkdnsite ./content --port 8080

# Fetch as markdown
curl -H "Accept: text/markdown" http://localhost:8080

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>
  <h1>Hello, world!</h1>
</body>
</html>

JSON

{
  "name": "mkdnsite",
  "version": "0.0.1",
  "dependencies": {
    "react": "^19.0.0",
    "react-markdown": "^9.0.0"
  }
}

Plain (no language)

This is a plain code block with no language specified.
It should still be rendered in a monospace font.

Tables (GFM)

FeatureBunNodeDeno
Runtime renderingYesYesYes
Content negotiationYesYesYes
Bun.markdown (fast path)YesNoNo
Native .ts supportYesYesYes

Table with alignment:

Left-alignedCenter-alignedRight-aligned
ContentContentContent
MoreMoreMore
DataDataData

Horizontal Rules

Content above the rule.


Content below the rule.


Another rule style.

Alerts (GFM)

NOTE

Useful information that users should know, even when skimming content.

TIP

Helpful advice for doing things better or more easily.

IMPORTANT

Key information users need to know to achieve their goal.

WARNING

Urgent info that needs immediate user attention to avoid problems.

CAUTION

Advises about risks or negative outcomes of certain actions.

Collapsible Sections

Click to expand

This content is hidden by default. It can contain markdown formatting, inline code, and other elements.

  • Item one
  • Item two
  • Item three
Another collapsible with a code block
const config = resolveConfig({
  site: { title: 'Hidden Config' }
})
This section is open by default

Use the open attribute on the <details> element to have it expanded initially.

Blockquote with Multiple Elements

Blockquote heading

This blockquote contains a heading, a paragraph, and a list:

  • Item one
  • Item two

And some inline code too.

Mermaid Diagrams

graph LR
    A[Request] --> B{Accept Header}
    B -->|text/html| C[React SSR]
    B -->|text/markdown| D[Raw MD]
    C --> E[HTML Response]
    D --> F[MD Response]

Math Expressions

Inline math: The quadratic formula is x=−b±b2−4ac2ax = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}x=2a−b±b2−4ac​​ and Euler's identity is eiπ+1=0e^{i\pi} + 1 = 0eiπ+1=0.

Display math (block):

∫−∞∞e−x2 dx=π\int_{-\infty}^{\infty} e^{-x^2} \, dx = \sqrt{\pi}∫−∞∞​e−x2dx=π​

A summation:

∑n=1∞1n2=π26\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}n=1∑∞​n21​=6π2​

Maxwell's equations in differential form:

∇⋅E=ρε0,∇×B=μ0J+μ0ε0∂E∂t\nabla \cdot \mathbf{E} = \frac{\rho}{\varepsilon_0}, \quad \nabla \times \mathbf{B} = \mu_0 \mathbf{J} + \mu_0 \varepsilon_0 \frac{\partial \mathbf{E}}{\partial t}∇⋅E=ε0​ρ​,∇×B=μ0​J+μ0​ε0​∂t∂E​

Emphasis and Inline Formatting

This paragraph exercises all inline formatting: bold, italic, bold italic, strikethrough, inline code, and a link.

Deeply Nested Lists

  1. First level
    • Second level unordered
      1. Third level ordered
        • Fourth level unordered
    • Another second level
  2. Back to first level

Long Code Block

/**
 * Create a portable fetch handler for mkdnsite.
 *
 * This handler conforms to the Web API fetch(request): Response
 * pattern, making it compatible with Bun, Node, Deno, and edge runtimes.
 */
export function createHandler (opts: HandlerOptions): (request: Request) => Promise<Response> {
  const { source, renderer, config } = opts

  return async function handler (request: Request): Promise<Response> {
    const url = new URL(request.url)
    const pathname = decodeURIComponent(url.pathname)

    // Content negotiation
    const format = negotiateFormat(request.headers.get('Accept'))

    if (format === 'markdown') {
      const page = await source.getPage(pathname)
      if (page == null) {
        return new Response('# 404\n\nPage not found.\n', {
          status: 404,
          headers: { 'Content-Type': 'text/markdown; charset=utf-8' }
        })
      }
      return new Response(page.body, {
        status: 200,
        headers: markdownHeaders(estimateTokens(page.body), config.negotiation.contentSignals)
      })
    }

    // ... HTML rendering via React SSR
  }
}