MDX in Astro: Use Components Inside Your Content Without Touching Raw Code

What MDX is, how it differs from Markdown, how to install it in Astro, and why it changes what's possible inside a blog post — with real component examples.

Quick answer

How do you use MDX in Astro?

Install the MDX integration with npx astro add mdx, update your Content Collections glob pattern to include .mdx files, then create .mdx files that import Astro components at the top and use them inline in the content like HTML tags.

Browser showing an Astro blog post with colored callout boxes rendered from MDX components
First-hand experience: Based on direct hands-on use. The callout boxes you're reading right now are MDX components — this article is itself a .mdx file using the same components it describes.

Plain Markdown covers most of what a blog post needs. Headings, paragraphs, lists, code blocks, blockquotes — that’s 90% of content writing.

The other 10% is where Markdown hits a wall.

A callout box that draws attention to an important warning. A pros and cons table specific to a review. A rating widget. An affiliate CTA styled to match the site. A FAQ section with expand/collapse behavior.

In WordPress, you’d reach for a Gutenberg block or a shortcode. In Astro, you reach for MDX.


What MDX actually is

MDX is Markdown that can import and use components.

That’s the entire definition. Everything that works in regular Markdown works in MDX. You just get one extra capability: you can put component tags in your content the same way you’d put HTML tags.

This is normal Markdown text.

<Callout type="warning" title="Watch out">
  This is a component rendered inside the content.
</Callout>

Back to normal Markdown.

The Callout component renders exactly where you placed it — styled, interactive if needed, consistent across every post that uses it.

For WordPress developers: this is what Gutenberg blocks do, but built with code you control instead of blocks registered by plugins. Define the component once, use it anywhere in MDX content.


Step 1: Install the MDX integration

npx astro add mdx

Astro’s CLI handles everything — it installs the package and updates astro.config.mjs automatically.

When prompted, answer Yes to all questions.

After installation, astro.config.mjs should look like this:

import { defineConfig } from 'astro/config'
import mdx from '@astrojs/mdx'

export default defineConfig({
  integrations: [mdx()],
})

Step 2: Update the Content Collections glob pattern

Your collection currently only picks up .md files. Update the pattern to include .mdx:

Open src/content.config.ts and change:

loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),

To:

loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }),

Do this for every collection that might contain MDX files.


Step 3: Build a Callout component

Create src/components/Callout.astro:

---
interface Props {
  type?: 'info' | 'warning' | 'tip'
  title?: string
}

const { type = 'info', title } = Astro.props
---

<div class={`callout callout-${type}`}>
  {title && <p class="callout-title">{title}</p>}
  <slot />
</div>

<style>
  .callout {
    padding: 1rem 1.25rem;
    border-left: 4px solid;
    border-radius: 0 var(--radius) var(--radius) 0;
    margin: 1.5rem 0;
  }

  .callout-info {
    border-color: #3b82f6;
    background: #eff6ff;
  }

  .callout-warning {
    border-color: #f59e0b;
    background: #fffbeb;
  }

  .callout-tip {
    border-color: #10b981;
    background: #ecfdf5;
  }

  .callout-title {
    font-weight: 700;
    font-size: 0.9375rem;
    margin: 0 0 0.5rem;
  }

  .callout-info .callout-title { color: #1d4ed8; }
  .callout-warning .callout-title { color: #b45309; }
  .callout-tip .callout-title { color: #047857; }

  .callout :global(p:last-child) {
    margin-bottom: 0;
  }
</style>

A few things worth understanding here:

type? with the ? — the ? means optional. If you don’t pass a type, it defaults to 'info'. TypeScript enforces that you can only pass 'info', 'warning', or 'tip' — nothing else.

<slot /> — the content you put between <Callout> opening and closing tags goes here. Same concept as the slot in BaseLayout.

:global(p:last-child) — scoped CSS normally can’t reach inside slotted content. :global() escapes the scope to target elements rendered inside <slot />. This removes the extra bottom margin from the last paragraph inside the callout.


Step 4: Create an MDX post

Create src/content/posts/what-is-mdx.mdx:

---
title: What is MDX and Why Does Astro Use It?
description: MDX lets you use components inside Markdown. Here is what that means in practice and why it matters for content sites.
publishedAt: 2026-06-17T00:00:00Z
---

import Callout from '../../components/Callout.astro'

If you've written content in Markdown before, you know its limits.

Markdown is great for text. But the moment you need something custom — a callout box, a rating widget, a comparison table — Markdown runs out of tools.

That's where MDX comes in.

## What is MDX?

MDX is Markdown with the ability to import and use components directly inside your content.

<Callout type="info" title="Simple definition">
  MDX = Markdown + JSX. Write text like Markdown, drop in components like HTML tags.
  Astro supports it natively with one integration.
</Callout>

## Why use MDX over plain Markdown?

Without MDX, you have two bad options when you need custom UI in a post:

- Write raw HTML inline — ugly, hard to maintain, breaks the writing flow
- Put all UI logic in the page template — inflexible, can't vary per post

MDX gives you a third option: define components once, use them anywhere in your content.

<Callout type="warning" title="Content Collections note">
  When using MDX with Content Collections, update your glob pattern to match both
  file types: `**/*.{md,mdx}`. Without this, getCollection() won't find your .mdx files.
</Callout>

## When to use MDX vs plain Markdown

Use `.md` for text-only posts. Use `.mdx` when you need UI components.

<Callout type="tip" title="You're reading an MDX file right now">
  This post is a .mdx file. The colored boxes are Callout components imported at
  the top and placed inline — exactly how MDX is meant to work.
</Callout>

Important: The component import goes after the frontmatter dashes, before the content:

---
frontmatter fields here
---

import Callout from '../../components/Callout.astro'

Content starts here.

The import path is relative to the content file’s location. From src/content/posts/, going up two levels (../../) reaches src/, then down to components/Callout.astro.


The result

Visit localhost:4321/blog/what-is-mdx and you should see three callout boxes rendered inline with the content — blue for info, yellow for warning, green for tip.


How this compares to WordPress shortcodes

WordPress shortcodes have been solving this problem since 2008:

[callout type="info" title="Note"]
This is a callout box.
[/callout]

MDX components serve the same purpose with a few differences:

WordPress shortcodesMDX components
Syntax[shortcode]<Component />
RegistrationPHP add_shortcode()Import at top of file
ScopeGlobal (any post/page)Per-file import
StylingPlugin CSSYour CSS
DependencyPlugin must be activeComponent file exists

MDX components are more explicit — you import what you use in each file. That’s slightly more verbose per file, but means you always know exactly what a post depends on. Open the file, see the imports, know the dependencies.


When NOT to use MDX

MDX has a tradeoff: the files are less portable. A .md file is pure text that renders in any Markdown processor. A .mdx file with Astro component imports only works in Astro — it won’t render in GitHub markdown preview or most other contexts.

For content that might be moved or reused elsewhere, stick to .md.

For content that’s definitively part of this Astro site — reviews, guides, comparison posts — MDX is worth it when the components genuinely improve the reading experience.

The rule: don’t add MDX for its own sake. Add it when a specific component makes a specific post significantly better. Otherwise, .md is simpler.


What’s next: Building specialized content types

Now that you can drop any component into content, the next articles build the specific layouts and components that make review pages, comparison pages, and guide pages genuinely useful.

A review page with a real rating widget, structured pros and cons, and a verdict box. A comparison page with a side-by-side table. A guide with a difficulty badge and step-by-step layout.

The pattern is always the same: define the component, import it, use it.


Frequently Asked Questions

What is the difference between Markdown and MDX?
Markdown is plain text with formatting syntax that renders to HTML. MDX is Markdown that can also import and use components directly inside the content. Everything that works in Markdown works in MDX — you just get the additional ability to drop in component tags anywhere in the text.
When should I use .md vs .mdx?
Use .md for text-only content that only needs headings, paragraphs, lists, and code blocks. Use .mdx when you need UI components inline — callout boxes, rating widgets, or comparison tables. Do not convert everything to MDX by default. Plain Markdown is simpler and more portable.
Do I need React to use MDX in Astro?
No. MDX in Astro works with native .astro components. You don't need React, Vue, or any JavaScript framework. The JSX-like syntax in MDX files is used to reference components, but those components are plain Astro files.
How is MDX different from WordPress shortcodes?
Both insert UI elements inside post content. WordPress shortcodes use [bracket] syntax and are registered globally via PHP. MDX uses JSX component syntax and imports are per-file. MDX components are code you control — no plugin dependency, no global registration required.
Can I use MDX files in Content Collections?
Yes. Update the glob pattern in your collection definition to match both file types: pattern: '**/*.{md,mdx}'. After that change and a dev server restart, both .md and .mdx files are picked up by getCollection() and work identically.