Using MDX
This theme comes with the @astrojs/mdx integration installed and configured in your astro.config.mjs config file. MDX allows you to use JSX components directly in your Markdown content.
Why MDX?
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to mix JavaScript and UI Components into your Markdown content for things like interactive elements, callouts, and dynamic data.
Callout Components
Callouts are great for highlighting important information. Here are the available types:
Use callouts to draw attention to important information that readers shouldn’t miss.
This is a note callout. It’s useful for additional context or explanations.
Be careful! This warning callout indicates something that could cause issues.
This is a danger callout for critical warnings that require immediate attention.
Usage:
import Callout from '../../../components/mdx/Callout.astro';
<Callout type="tip" title="Custom Title">
Your content here...
</Callout>
Interactive Components
MDX supports React components with client-side interactivity. Use Astro’s client: directives to hydrate components.
Counter Example
This counter is a React component that maintains state:
You can customize the initial value and step:
Usage:
import { Counter } from '../../../components/mdx/Counter.tsx';
<Counter client:visible label="Clicks" />
<Counter client:visible initialValue={10} step={5} label="Score" />
The client:visible directive means the component only hydrates when it enters the viewport, improving performance.
Image Components
This theme includes several image components for enhanced UX, accessibility, and SEO.
Figure with Caption
Use Figure for images that need semantic captions:
Usage:
import Figure from '../../../components/mdx/Figure.astro';
import myImage from '../../../assets/my-image.jpg';
<Figure
src={myImage}
alt="Description of the image"
caption="Caption text shown below"
credit="Optional credit"
/>
Lightbox / Zoom
Click the image below to open it in a fullscreen lightbox. Press Escape to close.
Usage:
import { Lightbox } from '../../../components/mdx/Lightbox.tsx';
<Lightbox
client:visible
src="/path/to/image.jpg"
alt="Description"
caption="Optional caption"
/>
Image Gallery
A responsive grid gallery with built-in lightbox navigation. Use Arrow keys to navigate, Escape to close.
Usage:
import { Gallery } from '../../../components/mdx/Gallery.tsx';
<Gallery
client:visible
columns={3} // 2, 3, or 4
gap="md" // sm, md, or lg
images={[
{ src: "/img1.jpg", alt: "Description 1", caption: "Caption 1" },
{ src: "/img2.jpg", alt: "Description 2", caption: "Caption 2" },
]}
/>
Before/After Comparison Slider
Drag the slider or use Arrow keys to compare images:
After
BeforeUsage:
import { BeforeAfter } from '../../../components/mdx/BeforeAfter.tsx';
<BeforeAfter
client:visible
beforeSrc="/before.jpg"
afterSrc="/after.jpg"
beforeAlt="Before description"
afterAlt="After description"
beforeLabel="Before"
afterLabel="After"
/>
Blur-up Placeholder
Images load with a blurred placeholder for better perceived performance:
Usage:
import BlurImage from '../../../components/mdx/BlurImage.astro';
import myImage from '../../../assets/my-image.jpg';
<BlurImage src={myImage} alt="Description" />
All interactive image components use client:visible to only hydrate when they enter the viewport, keeping your pages fast!
Video Components
This theme includes two video components: one for embedding YouTube/Vimeo videos and another for self-hosted videos.
VideoEmbed (YouTube/Vimeo)
Embed videos from YouTube or Vimeo with privacy-enhanced mode:
Use hideControls={true} to completely hide the control bar for a cinematic look (click video to play/pause):
Usage:
import { VideoEmbed } from '../../../components/mdx/VideoEmbed.tsx';
<VideoEmbed
client:visible
src="https://www.youtube.com/watch?v=VIDEO_ID"
title="Video title for accessibility"
aspectRatio="16:9" // 16:9, 4:3, 1:1, or 9:16
minimal={true} // Hide branding, annotations, fullscreen
hideControls={true} // Hide control bar completely
autoplay={false}
muted={false}
loop={false}
start={30} // Start at 30 seconds (YouTube only)
/>
VideoEmbed shows a thumbnail preview and only loads the video player when clicked. It uses privacy-enhanced URLs (youtube-nocookie.com and Vimeo’s DNT mode) to respect visitor privacy.
Self-Hosted Video
For videos hosted on your own server, use the Video component with native HTML5 controls:
Usage:
import Video from '../../../components/mdx/Video.astro';
import posterImage from '../../../assets/video-poster.jpg';
<Video
src="/videos/my-video.mp4"
poster={posterImage}
title="Video caption"
aspectRatio="16:9"
autoplay={false}
muted={false}
loop={false}
controls={true}
preload="metadata" // none, metadata, or auto
/>
{/* Multiple formats for browser compatibility */}
<Video
src={["/videos/video.webm", "/videos/video.mp4"]}
title="Video with format fallbacks"
/>
For autoplay to work without user interaction, videos must be muted. The Video component handles this automatically.
Inline JavaScript Expressions
MDX allows you to embed JavaScript expressions directly in your content:
- Current year: 2026
- Math calculation: 2 + 2 = 4
- Conditional: Yes!
You can also use variables:
- Project: OXCOMY
- Version: 1.0.0
Using Astro Components
You can import and use Astro components in MDX. Here’s a formatted date component:
Today’s date:
A specific date:
Combining Markdown and JSX
One of MDX’s strengths is seamlessly mixing Markdown syntax with JSX:
This is a styled container
You can write regular Markdown inside JSX elements:
- List items work
- Italic and bold too
- Even
inline code
Client Directives Reference
When using React/Vue/Svelte components, choose the right hydration strategy:
| Directive | When it Hydrates |
|---|---|
client:load | Immediately on page load |
client:idle | When the browser is idle |
client:visible | When component enters viewport |
client:media | When media query matches |
client:only | Skips server render, client-only |
More Resources
Remember: Components in MDX render as static HTML by default. Add a client:* directive for interactivity!
0 Comments