Skip to content
CtrlK

Markdown & MDX

Write Markdown and MDX that can stay simple until a page needs real React.

On this page

Ardo is Markdown-first, but it does not trap you in Markdown. Start with plain prose and code blocks; when a page needs a live React example, a custom component, or a design-system callout, switch to MDX without changing tools.

Page Filenames

Files in app/routes/ become page URLs. Use descriptive names such as guide/getting-started.mdx or guide/c-sharp.mdx. Ardo rejects # and ? in route filenames and directory names because they delimit URL fragments and queries. Use those characters in page titles and content instead.

Basic Syntax

Headings

# Heading 1

## Heading 2

### Heading 3

#### Heading 4

Emphasis

*italic* or _italic_
**bold** or __bold__
***bold and italic***
~~strikethrough~~

Lists

- Unordered item 1
- Unordered item 2
  - Nested item

1. Ordered item 1
2. Ordered item 2
[Link text](https://example.com)
![Alt text](/path/to/image.png)

GitHub Flavored Markdown

Ardo supports GitHub Flavored Markdown (GFM) out of the box.

Tables

FeatureSupported
TablesYes
Task ListsYes
AutolinksYes

Task Lists

  • Write documentation
  • Add syntax highlighting
  • Deploy to production

URLs are automatically converted to links: https://vite.dev

Syntax Highlighting

Ferromark highlights Markdown code blocks at build time with its native Ferriki integration. Standard language grammars and themes are downloaded and cached on first use. Examples include:

CategoryLanguages
Webjavascript, typescript, jsx, tsx, html, css, scss
Data & configjson, jsonc, yaml, toml, xml, graphql
Markdownmarkdown, mdx
Shell & DevOpsbash, shell, dockerfile
General purposepython, rust, go, sql, diff
interface User {
  id: string
  name: string
  email: string
}

function greetUser(user: User): string {
  return `Hello, ${user.name}!`
}

Line Highlighting

Highlight specific lines using the {lines} syntax:

function example() {
  const highlighted = true
  const normal = false
  const alsoHighlighted = true
  const highlighted = true
  const notHighlighted = false
}

Line Numbers

Enable line numbers with showLineNumbers:

const first = 1
const second = 2
const third = 3

Title

Add a title to your code block. It combines with line highlighting:

ardo.config.ts
import { defineArdoConfig } from "ardo/config"

export default defineArdoConfig({
  api: {
    entryPoints: ["./src/index.ts"],
    tsconfig: "./tsconfig.json",
    title: "API Reference",
  },
})

Prompts

Use the prompt language for text that readers copy into a coding agent. Its lines wrap instead of scrolling sideways, so write each paragraph on one line:

```prompt
Add a Deployment page that explains the GitHub Pages workflow of this repository.
```

Code Groups

Display multiple code variants in tabs:

function greet(name) {
  return `Hello, ${name}!`
}

Callouts

Plain Markdown, no imports: Ardo recognizes GitHub's alert syntax and maps each type to the matching callout.

> [!NOTE]
> Useful information that users should know.

> [!TIP]
> Helpful advice for doing things better.

> [!IMPORTANT]
> Key information users need to know.

> [!WARNING]
> Urgent info that needs immediate attention.

> [!CAUTION]
> Negative potential consequences of an action.

Renders as:

Note

Useful information that users should know.

Tip

Helpful advice for doing things better.

Important

Key information users need to know.

Warning

Urgent info that needs immediate attention.

Caution

Negative potential consequences of an action.

For a custom title, use the callout components such as <Tip title="Pro Tip"> described in Components.

Components

Ardo's components work in MDX without imports: callouts, badges, cards, tabs, accordions, steps, code blocks, and icons. Components shows each one live, with its MDX and its props.

Your Own Components

Import React components into MDX and use them like HTML. Keep them outside app/routes/, for example in app/components/, because every .tsx file in the routes folder becomes a page:

app/routes/guide/my-page.mdx
import { MyComponent } from "../../components/MyComponent"

# My Page

<MyComponent prop="value" />

Reusable Snippets

Ardo uses standard MDX modules, so reusable content snippets work without a custom syntax or plugin. Put shared fragments in a snippets/ directory and import them wherever you need the same content.

For example, define a shared prerequisite note in app/snippets/prerequisites.mdx. Snippet files do not need frontmatter when they are rendered inside another page:

<Tip title="Before you start">
  This guide assumes you have {props.runtime} installed and can run packages with{" "}
  {props.packageManager}.
</Tip>

Then import and render it from any MDX page inside ArdoBareContent so the snippet does not bring its own page wrapper:

import { ArdoBareContent } from "ardo/ui"
import Prerequisites from "../../snippets/prerequisites.mdx"

# Getting Started

<ArdoBareContent>
  <Prerequisites runtime="Node.js 22.22.0+" packageManager="pnpm" />
</ArdoBareContent>

Snippets can receive props in normal MDX expressions, compose Ardo components, and live alongside the rest of your application code. Because they are just MDX modules, Vite handles hot reloads and imports the same way as other route dependencies.

Use a relative import that matches your route depth. For example, a page in app/routes/guide/ usually imports ../../snippets/prerequisites.mdx.

In TSX Routes

When you import an .mdx file into a .tsx route, it normally renders inside the full Content wrapper (article, header, footer, navigation). This is useful for full pages, but snippets usually need to render without that page chrome. To embed an imported MDX file as a plain content snippet in TSX, wrap it in ArdoBareContent:

import { ArdoBareContent } from "ardo/ui"
import MySnippet from "./snippet.mdx"

export default function MyPage() {
  return (
    <ArdoBareContent>
      <MySnippet />
    </ArdoBareContent>
  )
}

How MDX Compiles

Both .md and .mdx routes are parsed by Ferromark. MDX adds JavaScript expressions, imports, exports, and JSX components; React compiles the generated JSX through Vite. Use YAML frontmatter for page metadata.

Ferromark compiles each route into an MDX module, following the same contract as @mdx-js/mdx:

  • import and export blocks are JavaScript. TypeScript syntax there is a compile error; move typed code into a .ts module and import it.
  • A component you import is used as imported. Every other component comes from Ardo's components, then from the components prop. An undefined component fails with an error that names it.
  • An export default becomes the page layout and replaces Ardo's content wrapper, so the page renders inside your layout instead of ArdoDocContent.
  • JSX inside a JavaScript expression, such as {items.map((item) => <Card />)}, does not see Ardo's components. Import the component you use there.
  • Generated code reserves _components, _createMdxContent, _missingMdxReference, _provideComponents, MDXLayout, MDXContent, _ArdoPageDataProvider, _ardoFrontmatter, _ardoRoute, and _ardoToc. Declaring one of them is a compile error.

Frontmatter

Add metadata to your pages using YAML frontmatter:

---
title: Page Title
description: Page description for SEO
order: 2
---

See the Frontmatter Reference for all available options.

Edit this page on GitHubLast updated: October 23, 2026