MDX cheat sheet

Updated

MDX is markdown plus three additions: JSX tags (<Chart />), expressions in braces ({Math.PI}) and ESM import/export. In exchange it drops three markdown features — indented code, autolinks and raw HTML — and makes < and { reserved characters. Syntax below follows the official mdxjs.com docs for MDX 3; snippets are shown as source only because they need a compiler to run.

Markdown vs MDX: what changes

You writeMarkdown (GFM)MDX
Indented code (4 spaces)Code blockParagraph — use ``` fences
Bare URL / <https://…>AutolinkNot a link — write [text](url)
<div>, <img>, <br>Raw HTML passed throughJSX — must be well-formed and self-closed (<img />, <br />)
<!-- comment -->HiddenError — use {/* comment */}
Literal < or {Plain textEscape: \< \{ or {'<'} {'{'}
class="x" on a tagPassed throughclassName="x" in React (attribute names follow the framework)
style="color:red"Passed throughstyle={{color: 'red'}}
Tables, ~~strike~~, task lists, footnotesGFM (most renderers)Off until remark-gfm is added
YAML front matterIgnored or parsed by the siteNeeds remark-frontmatter (and remark-mdx-frontmatter to expose it)
import / export linesPlain textESM — must be at the top level, not indented
{Math.PI}Plain textEvaluated expression
Markdown inside <Tag>…</Tag>n/aInline markdown works on the same line; block markdown needs its own lines inside the tag

JSX in markdown

Any JSX tag can sit between markdown blocks or inside a paragraph. The preview column shows plain markdown equivalents, since the site renderer does not execute components.

A component in prose

import {Chart} from './chart.js'

# Quarterly numbers

Revenue grew **12%**.

<Chart data={[10, 12, 15]} />

See the <Tooltip tip="year over year">YoY</Tooltip> note.

Block JSX goes on its own line between blank lines; inline JSX stays inside the sentence.

Markdown inside JSX: same line vs own lines

<div>*this is emphasis*</div>

<div># this is not a heading</div>

<div>
  # this IS a heading
</div>

Inline markdown works when text and tags share a line; block syntax (headings, lists) only works on its own lines inside the tag.

Self-closing and well-formed tags

<img src="/pic.png" alt="A picture" />
<br />
<Box>
  Content
</Box>

Unlike HTML in markdown, every tag must close; <img> without the slash is a syntax error.

Attribute names follow the framework

<section className="note" style={{color: 'tomato'}} aria-label="Note">
  Text
</section>

React wants className and a style object; copying class="…" style="…" from HTML fails.

Imports and exports

Import components and data

import {Chart} from './chart.js'
import data from './data.json'
import {External} from './some/place.js'

import/export must start at column 0 and may contain blank lines; they are hoisted out of the content.

Export values other files can import

export const meta = {title: 'Release notes', date: '2026-10-15'}

export function Note({children}) {
  return <aside className="note">{children}</aside>
}

<Note>Locally defined components work too.</Note>

Named exports are available to whoever imports the .mdx file; the content itself is the default export.

Layout via default export

export default function Layout({children}) {
  return <main className="prose">{children}</main>
}

# This heading renders inside <main>

A local default export wraps all content and overrides a components.wrapper passed from outside.

Expressions in braces

Inline and block expressions

Two π is {Math.PI * 2}.

{(function () {
  const items = ['a', 'b']
  return <ul>{items.map((i) => <li key={i}>{i}</li>)}</ul>
})()}

Braces hold any JavaScript expression; multi-statement logic goes in an IIFE.

Props passed to the MDX component

# Hello {props.name.toUpperCase()}

The current year is {props.year}

Rendered as <Content name="Mars" year={2026} />; the document reads them as props.

Comments and escapes

{/* A comment, invisible in output */}

Templates use \{braces\} and 1 \< 2, or {'{'}braces{'}'} and 1 {'<'} 2.

HTML comments are not allowed; \< and \{ (or string expressions) are the only ways to print those characters outside code.

Components as headings and layouts

Replace markdown elements from outside

import Content from './post.mdx'

<Content
  components={{
    h1: (props) => <h1 className="title" {...props} />,
    a: Link,
    code: Code,
    wrapper: ({children}) => <article>{children}</article>,
  }}
/>

Keys named after HTML tags (h1, a, pre, code, img) swap the element markdown generates; wrapper wraps everything.

Map h1 to h2 in one line

<Content components={{h1: 'h2'}} />

A string value works too — useful when the page template already renders the H1.

Provider for nested MDX (React)

import {MDXProvider} from '@mdx-js/react'

<MDXProvider components={{h1: Title}}>
  <Content />
</MDXProvider>

Only needed when MDX files import other MDX files; otherwise pass components directly (Next.js uses mdx-components.tsx instead).

Gotchas

No indented code blocks

    console.log(1) // this is a paragraph in MDX!

```js
console.log(1) // this is a code block
```

Four-space indentation is ignored so JSX can be indented; always use fences.

No autolinks

https://mdxjs.com is plain text.

[mdxjs.com](https://mdxjs.com) is a link.

Bare URLs and <https://…> are not links in MDX; <https://…> is even a JSX parse error.

Tags cannot span blocks

<a href="/x">

# Heading inside a link

</a>

An opening tag in one paragraph and its closing tag in another raise a misnesting error; keep the whole element in one block.

GFM needs a plugin

import remarkGfm from 'remark-gfm'
// compile(source, {remarkPlugins: [remarkGfm]})

| a | b |
|---|---|
| 1 | 2 |

~~strike~~  - [x] task

Without remark-gfm the table is a paragraph of pipes and the tildes are literal.

MDX 1 vs 2 vs 3

ChangeMDX 2 (2022)MDX 3 (2023)
SyntaxJSX no longer needs blank lines around it and may be indented; expressions {…} added; markdown ambiguities (indented code, autolinks, HTML) removedUnchanged
GFMOff by default; add remark-gfmSame
PackagesESM-only (no require())Node 16+ required
ComponentsinlineCode removed (use code); dotted names like ol.li removedMDXContext and withMDXComponents replaced by useMDXComponents
Options—useDynamicImport removed (now default); baseUrl needed for evaluate/run when the file uses import/export
Loaders—@mdx-js/register replaced by @mdx-js/node-loader; classic JSX runtime deprecated in favour of automatic

Sources: migrating to MDX 2 and migrating to MDX 3.

Frameworks: Next.js, Docusaurus, Astro and others

FrameworkPackageSetupNotes
Next.js (App Router)@next/mdxWrap next.config with createMDX(); add md and mdx to pageExtensions; add mdx-components.tsx for custom components.mdxjs.com advises against providerImportSource and @mdx-js/react with Next.
Docusaurusbuilt inWrite .mdx in docs/ or blog/; import components at the top of the file.Docusaurus 3 uses MDX 3; front matter and admonitions (:::note) are supported.
Astro@astrojs/mdxnpx astro add mdx; put .mdx in src/pages or a content collection.Astro components and framework components can be used; expressions run at build time.
Vite@mdx-js/rollupAdd mdx() to plugins in vite.config.js; with @vitejs/plugin-react use enforce: "pre".Same plugin serves Rollup builds.
Gatsbygatsby-plugin-mdxAdd the plugin to gatsby-config and source .mdx files.Gatsby maintains its own integration.
Any bundler / Node@mdx-js/mdx, @mdx-js/node-loader, @mdx-js/esbuild, @mdx-js/loader (webpack)compile() or evaluate() from @mdx-js/mdx; loaders for esbuild, webpack and Node.Core packages are ESM-only since MDX 2.

Source: mdxjs.com getting started.

What is MDX?

MDX is markdown that can contain JSX, JavaScript expressions in curly braces, and ESM import/export statements. A .mdx file compiles to a JavaScript component, so React (or Preact, Vue, Svelte via the JSX runtime) components can be used inline in prose. Markdown support is CommonMark by default; GFM tables, task lists and footnotes need the remark-gfm plugin.

How do I use a React component inside MDX?

Import it at the top of the file and write it as a JSX tag anywhere in the content: import {Chart} from './chart.js' followed by <Chart data={[1, 2, 3]} /> on its own line. Components can also be passed in through the components prop of the compiled MDX component, or defined locally with export.

Why does MDX fail on a < or { character in my text?

In MDX, < starts a JSX tag and { starts an expression, so a bare "a < b" or "use {braces}" is a syntax error. Escape them as \< and \{, or write them as expressions: {'<'} and {'{'}. Inside inline code and fenced code blocks they are fine without escaping.

Why does an HTML comment break MDX?

HTML syntax does not work in MDX; it is replaced by JSX. <!-- comment --> is therefore invalid. Use a JavaScript comment inside braces instead: {/* comment */}. The same rule means <img> must be self-closed as <img />.

Markdown comment guide →

What markdown does not work in MDX?

Three things from regular markdown are turned off: indented code blocks (four-space indentation is a paragraph, so JSX can be indented), autolinks (write [text](https://url) instead of a bare URL or <https://url>) and HTML (use JSX). GFM extensions such as tables and strikethrough are off unless remark-gfm is added.

What changed between MDX 2 and MDX 3?

MDX 3 (2023) is a small release: Node 16 or later is required, the useDynamicImport option was removed because its behaviour became the default, evaluate/run need a baseUrl when the MDX uses import/export, MDXContext and withMDXComponents are replaced by useMDXComponents, and @mdx-js/register is replaced by @mdx-js/node-loader. The syntax itself did not change. MDX 2 (2022) was the big break: packages became ESM-only, GFM became opt-in, the inlineCode component and dotted component names like ol.li were removed, and JSX no longer needs blank lines around it.

How do I add a layout to an MDX file?

Export a default function that receives children: export default function Layout({children}) { return <main>{children}</main> }. Alternatively pass the layout as components.wrapper from the outside; a local default export takes precedence over the wrapper.

Related

Markdown cheat sheet · Pandoc markdown · Front matter generator · Markdown lint · Markdown to HTML · Code block guide · Comment guide