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
Links and Images
[Link text](https://example.com)

GitHub Flavored Markdown
Ardo supports GitHub Flavored Markdown (GFM) out of the box.
Tables
| Feature | Supported |
|---|---|
| Tables | Yes |
| Task Lists | Yes |
| Autolinks | Yes |
Task Lists
- Write documentation
- Add syntax highlighting
- Deploy to production
Autolinks
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:
| Category | Languages |
|---|---|
| Web | javascript, typescript, jsx, tsx, html, css, scss |
| Data & config | json, jsonc, yaml, toml, xml, graphql |
| Markdown | markdown, mdx |
| Shell & DevOps | bash, shell, dockerfile |
| General purpose | python, 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:
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
Tip
Important
Warning
Caution
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:
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:
importandexportblocks are JavaScript. TypeScript syntax there is a compile error; move typed code into a.tsmodule and import it.- A component you import is used as imported. Every other component comes from Ardo's components, then from the
componentsprop. An undefined component fails with an error that names it. - An
export defaultbecomes the page layout and replaces Ardo's content wrapper, so the page renders inside your layout instead ofArdoDocContent. - 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.