Table Of Contents
MDX with Astro components gives you the best of both worlds: the simplicity of markdown for writing and the flexibility of components for rich, interactive content. Start simple with components like TwoColumns and Callout, then build more complex ones as your needs grow.
The web is a flexible enough domain that I think it belongs in the realm of architecture. A city where all buildings look alike has a soul-crushing quality about it. The same is true, I think, of the web..
Two Columns example
Left Column
This is the left column content. You can use:
- Markdown formatting
- Bold and italic text
- Lists
- Code blocks
const example = 'code';Right Column
This is the right column content. You can also use:
- Numbered lists
- Links like this
- Images
- Any markdown!
Even blockquotes work here.
And here’s regular content after the columns that goes full-width again.
Another Section
You can even customize the gap between columns by passing a gap prop! Here it’s set at 3rem.
The columns automatically stack on mobile devices (screens under 768px).
Using Astro Components in MDX Blog Posts
What is MDX?
MDX is markdown with the ability to import and use components. It combines the simplicity of markdown with the power of React-like components, making it perfect for blog posts that need more than basic formatting.
Converting Markdown to MDX
Simply rename your file from .md to .mdx:
blog-post.md → blog-post.mdx
That’s it! Your existing markdown will still work, but now you can also import components.
Importing Components
Understanding Import Paths
When importing components in MDX, you need to specify the correct relative path from your blog post to the component:
If your structure is:
src/
components/
TwoColumns.astro
content/
blog/
my-post.mdx
Your import should be:
import TwoColumns from '../../../components/TwoColumns.astro';
Path breakdown:
../- goes up fromblog/tocontent/../../- goes up fromcontent/tosrc/../../../components/- enters thecomponents/folder
Alternative: Path Aliases
If your tsconfig.json has path aliases configured:
{
"compilerOptions": {
"paths": {
"@/*": ["src/*"]
}
}
}
You can use cleaner imports:
import TwoColumns from '@/components/TwoColumns.astro';
Using Components in MDX
Basic Usage
---
title: "My Blog Post"
---
import TwoColumns from '@/components/TwoColumns.astro';
# My Post Title
Regular markdown content here.
<TwoColumns>
<div slot="left">
Left column content
</div>
<div slot="right">
Right column content
</div>
</TwoColumns>
More markdown content continues...
Named Slots
Astro components use named slots to organize content:
<TwoColumns>
<div slot="left">...</div>
<div slot="right">...</div>
</TwoColumns>
The slot attribute tells the component where to place that content.
Passing Props
You can pass properties to components:
<TwoColumns gap="3rem" class="custom-style">
...
</TwoColumns>
Nesting Markdown
You can use full markdown syntax inside component slots:
<TwoColumns>
<div slot="right">
## Heading
Regular **markdown** works here:
- Lists
- Links
- Code blocks
``js
const code = 'examples';
``
</div>
<div slot="right">
More markdown here!
</div>
</TwoColumns>
Useful Component Ideas
1. Callout/Alert Box
Component: Callout.astro
---
interface Props {
type?: 'info' | 'warning' | 'success' | 'error';
title?: string;
}
const { type = 'info', title } = Astro.props;
---
<div class={`callout callout-${type}`}>
{title && <div class="callout-title">{title}</div>}
<div class="callout-content">
<slot />
</div>
</div>
<style>
.callout {
padding: 1.5rem;
margin: 1.5rem 0;
border-left: 4px solid;
border-radius: 4px;
}
.callout-info {
background: #e3f2fd;
border-color: #2196f3;
}
.callout-warning {
background: #fff3e0;
border-color: #ff9800;
}
.callout-success {
background: #e8f5e9;
border-color: #4caf50;
}
.callout-error {
background: #ffebee;
border-color: #f44336;
}
.callout-title {
font-weight: bold;
margin-bottom: 0.5rem;
}
</style>
Usage:
<Callout type="warning" title="Important">
Make sure to backup your data before proceeding!
</Callout>
2. Code Comparison
Component: CodeCompare.astro
---
interface Props {
before?: string;
after?: string;
}
const { before = 'Before', after = 'After' } = Astro.props;
---
<div class="code-compare">
<div class="compare-side">
<h4>{before}</h4>
<slot name="before" />
</div>
<div class="compare-side">
<h4>{after}</h4>
<slot name="after" />
</div>
</div>
<style>
.code-compare {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin: 2rem 0;
}
.compare-side h4 {
margin-top: 0;
color: #666;
}
@media (max-width: 768px) {
.code-compare {
grid-template-columns: 1fr;
}
}
</style>
Usage:
<CodeCompare before="Old Way" after="New Way">
<div slot="before">
``js
const x = function() {
return 'old';
}
``
</div>
<div slot="after">
``js
const x = () => 'new';
``
</div>
</CodeCompare>
3. Image with Caption
Component: Figure.astro
---
interface Props {
src: string;
alt: string;
caption?: string;
}
const { src, alt, caption } = Astro.props;
---
<figure>
<img src={src} alt={alt} />
{caption && <figcaption>{caption}</figcaption>}
</figure>
<style>
figure {
margin: 2rem 0;
text-align: center;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
figcaption {
margin-top: 0.5rem;
font-style: italic;
color: #666;
font-size: 0.9rem;
}
</style>
Usage:
<Figure
src="/images/example.jpg"
alt="Example diagram"
caption="Figure 1: This shows the component architecture"
/>
4. Expandable Section
Component: Details.astro
---
interface Props {
summary: string;
open?: boolean;
}
const { summary, open = false } = Astro.props;
---
<details open={open}>
<summary>{summary}</summary>
<div class="details-content">
<slot />
</div>
</details>
<style>
details {
margin: 1.5rem 0;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
summary {
cursor: pointer;
font-weight: bold;
user-select: none;
}
summary:hover {
color: #2196f3;
}
.details-content {
margin-top: 1rem;
}
</style>
Usage:
<Details summary="Click to see advanced options">
Here are some advanced configuration options you might need...
</Details>
5. Side-by-Side Images
Component: ImageGrid.astro
---
interface Props {
columns?: number;
gap?: string;
}
const { columns = 2, gap = '1rem' } = Astro.props;
---
<div class="image-grid" style={`--columns: ${columns}; --gap: ${gap}`}>
<slot />
</div>
<style>
.image-grid {
display: grid;
grid-template-columns: repeat(var(--columns, 2), 1fr);
gap: var(--gap, 1rem);
margin: 2rem 0;
}
.image-grid :global(img) {
width: 100%;
height: auto;
border-radius: 8px;
}
@media (max-width: 768px) {
.image-grid {
grid-template-columns: 1fr;
}
}
</style>
Usage:
<ImageGrid columns={3} gap="2rem">



</ImageGrid>
6. YouTube Embed
Component: YouTube.astro
---
interface Props {
id: string;
title?: string;
}
const { id, title = 'YouTube video' } = Astro.props;
---
<div class="video-container">
<iframe
src={`https://www.youtube.com/embed/${id}`}
title={title}
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
<style>
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
overflow: hidden;
margin: 2rem 0;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 8px;
}
</style>
Usage:
<YouTube id="dQw4w9WgXcQ" title="Example Video" />
Best Practices
1. Keep Components Reusable
Design components that can be used across multiple blog posts, not just for one specific case.
2. Make Components Responsive
Always include mobile breakpoints in your component styles.
3. Provide Sensible Defaults
Use default prop values so components work with minimal configuration.
4. Document Your Components
Add comments explaining what props are available and how to use them.
5. Use Semantic HTML
Choose appropriate HTML elements (figure, details, aside, etc.) for better accessibility.
6. Style Isolation
Use scoped styles in Astro components to avoid CSS conflicts.
Troubleshooting
Component Not Found
- Check your import path is correct
- Verify the component file exists
- Make sure you’re using
.mdxnot.md
Markdown Not Rendering Inside Component
- Ensure you have blank lines around markdown content
- Check that slots are properly defined in the component
Styles Not Applying
- Verify
<style>tags are in the Astro component - Check for CSS specificity conflicts
- Use
:global()for styling slotted content if needed
Conclusion
MDX with Astro components gives you the best of both worlds: the simplicity of markdown for writing and the flexibility of components for rich, interactive content. Start simple with components like TwoColumns and Callout, then build more complex ones as your needs grow.