MDX Markdown: JSX Components in Markdown Explained
September 11, 2026 · 9 min read
MDX Markdown: JSX Components in Markdown Explained
MDX markdown is Markdown that also accepts JSX components, JavaScript expressions, and ES module imports. Files use the .mdx extension and compile to a JavaScript module instead of an HTML fragment. This guide shows what MDX adds, what breaks when you rename a .md file, and which parts of an .mdx file a normal Markdown editor still renders.
What Does MDX Add to Plain Markdown?
MDX keeps every Markdown construct you already use and layers four things on top. The official docs describe the format as markdown combined with JSX, something close to literate programming. The current major version is MDX 3, released on 24 October 2023, and it requires Node.js 16 or newer.
- JSX tags.
<Chart year={2025} />drops a React (or Vue, Svelte, Preact) component into your prose. - Expressions. Anything inside curly braces is JavaScript:
{Math.PI * 2}renders as 6.28. - Imports and exports.
importpulls in components or data;exportlets the file publish values such asmetadata. - JavaScript comments. HTML comments are gone. You write
{/* hidden */}instead.
Under the hood, the compiler turns the whole file into a JavaScript module. The main content becomes the default export, a function component that takes props and returns JSX. Any other export in the file is exported too, which is how a blog index can read a post's title without rendering it.
The result is a superset of Markdown, not a replacement. If you're comfortable with Markdown versus HTML, think of MDX as the same idea with JSX in the seat that inline HTML used to occupy.
An Annotated MDX Markdown File
Here's a complete .mdx file that uses every feature once. Read the notes below it before you copy it.
import { Chart } from '../components/chart.jsx'
export const metadata = { title: 'Snowfall 2025', author: 'Dana' }
# Snowfall report
Total snowfall last winter was **{42 + 17} cm**, up from the year before.
<Chart year={2025} color="steelblue" />
{/* TODO: add the 2024 comparison */}
- Measured at the north station
- Rounded to the nearest centimetre
What each part compiles to:
- The
importline becomes a normal ES import at the top of the generated module. export const metadatais exported alongside the content, so a page can read the title without rendering the post.- The heading, bold text, and list compile to
h1,strong, andulelements, exactly as Markdown would. {42 + 17}is evaluated at render time and prints 59.<Chart ... />is a React element. The propyear={2025}is a number;color="steelblue"is a string.- The comment produces nothing in the output.
The whole file exports a default function component. You render it like any other component, and you can pass a components prop to swap h1 or a for your own implementations.
What Breaks When You Rename .md to .mdx?
This is the part nobody warns you about. MDX parses curly braces and angle brackets as code, so text that was harmless in Markdown now throws compile errors. Here's the full list from the MDX troubleshooting docs, with the fix for each.
| Plain Markdown habit | What MDX does | Fix |
|---|---|---|
A { or } in prose, such as use {name} | Tries to parse an expression; "Could not parse expression with acorn" | Write \{ and \} or put it in a code span |
A bare < followed by text, such as a < b | Opens a JSX tag; "Unexpected character in tag" | Write \< or use a code span |
<!-- comment --> | Fatal parse error | Use {/* comment */} |
| Indented code (four spaces) | Treated as a paragraph | Use fenced code blocks |
<img src="x.png"> or <br> | Unclosed JSX tag error | Self-close every tag: <img src="x.png" /> |
<div class="note"> | Renders, but React warns | Use className |
Bare autolink <https://example.com> | Parsed as a tag | Write [example](https://example.com) |
In our testing, the curly-brace rule catches the most people. Any .md file that documents templating syntax, shell expansions, or JSON will fail to compile until the braces are escaped or fenced. Everything inside a fenced code block is safe, which is why fenced code blocks are the reliable way to show JSX literally in MDX content.
The Markdown comments post covers the HTML comment tricks that stop working here.
Is MDX Better Than Markdown?
No. It's a different tool with a different cost. Plain Markdown renders anywhere: GitHub, Slack, Obsidian, a static site generator, a chat model. MDX only renders after a JavaScript build step that knows how to compile it, and every stray brace becomes a build failure instead of a typo.
Debugging is the other cost. A Markdown typo renders as slightly wrong text; an MDX typo stops the build with a message such as 'Unexpected character in tag' and a line number. The error names are consistent, so once you have seen each one you can fix it in seconds, but the first week feels unforgiving.
MDX wins when your content needs live components: a pricing calculator inside a docs page, an interactive chart in a blog post, a tabbed code sample. We prefer plain Markdown for anything that has to survive outside one codebase, and MDX only for pages that are already React.
One limitation to acknowledge: MDX files are not portable. Rename the extension back to .md and the imports show up as paragraph text.
So where does MDX earn its keep? It needs a framework that compiles it, and the popular hosts are:
- Next.js uses the
@next/mdxpackage. You install@next/mdx,@mdx-js/loader,@mdx-js/react, and@types/mdx, wrap your config increateMDX(), and addremarkPluginsandrehypePluginsunderoptions. The Next.js MDX guide has the full config; note that plugins must be named as strings when you build with Turbopack. - Docusaurus, Astro, and Gatsby all support
.mdxpages out of the box or with a first-party integration. - Storybook uses MDX for component documentation pages.
Content generated by AI assistants often arrives as MDX-flavoured Markdown with stray braces. If that's how you got here, the Markdown in AI post explains why models default to Markdown and how to clean it.
Which Parts of an MDX File Does a Plain Markdown Editor Render?
We pasted the annotated file above into a standard renderer to see what survives. The answer is predictable once you know MDX is a superset.
- Headings, emphasis, lists, links, fenced code render normally. This is plain Markdown.
importandexportlines render as ordinary paragraphs of text, because a Markdown parser has no idea they're code.- Expressions such as
{42 + 17}print literally, braces included. - JSX tags are treated as unknown HTML. Most sanitising renderers drop them; some show them as text.
- JavaScript comments print literally, braces and asterisks included.
GitHub behaves the same way. In our testing, GitHub renders .mdx files in the repository view with its regular Markdown renderer. The import and export lines appear as paragraphs, the {/* */} comments are visible, and component tags vanish. It's readable, but it's not what your site will show.
Try MDX Markdown in the Editor
Paste or edit the sample below. The Markdown parts render in the preview on the right. The import, expression, and comment lines show as plain text, and the component tag is dropped as unknown HTML. That shows you exactly which parts depend on a build step.
The live editor is a handy place to draft the prose half of an MDX page before you add components. The Markdown to HTML converter shows what the plain sections compile to.
Common MDX Markdown Mistakes
Writing class instead of className. The tag renders, but React logs a warning on every page load. JSX attributes are camelCase: className, onClick, htmlFor.
Leaving a brace unclosed. a { b fails with "Unexpected end of file in expression". Every opening brace needs a closing one, or a backslash in front of it.
Expecting YAML frontmatter to work. A --- block at the top of the file is not part of MDX. The Next.js docs state that @next/mdx does not support frontmatter by default, so the block renders as a rule and stray text instead of being read as data. Export a metadata object instead, or add the remark-frontmatter plugin and keep the block.
Putting a component inside a list item without indentation. MDX follows CommonMark container rules. A JSX block inside a list or blockquote must be indented to the item's content column, or the parser reports "Cannot close document, a token is still open".
MDX Markdown FAQ
MDX markdown gives you React components inside prose, at the price of a compiler and a stricter grammar. Escape your braces, self-close your tags, fence anything you want shown literally, and the format behaves. For the Markdown half of the job, draft and preview in the editor, then move the file into your framework and add the components last. The MDX documentation and its troubleshooting page cover the remaining edge cases.