MDX features

Use Astro components inside a post. This one shows cards, color swatches, and the highlighter.

#MDX #Guide

Why MDX

MDX is Markdown that can also use components. You write a post as usual, and wherever plain text isn’t enough, you drop in a component such as a card, a color swatch, or a highlighted phrase.

Those components are written with JSX, an HTML-like tag syntax such as <DemoCard /> that stands for a piece of UI. MDX is, in short, Markdown plus JSX.

Posts with a .mdx extension work like Markdown, with one addition: you can import Astro components and use them right in the text. Plain .md posts keep working, so you only reach for MDX when a post needs it.

Import components

Put the imports right after the frontmatter, then use the components anywhere below:

import Highlight from '@/components/common/Highlight.astro';
import DemoCard from '@/components/posts/DemoCard.astro';

Cards

DemoCard wraps content in a themed card. It has three variants, flat, raised, and interactive (the default):

Flat
Soft edges, no shadow.
Raised
Hard border, solid shadow.
Interactive
Hover over me.
<DemoCard variant="raised" class="p-5">
  <div class="font-heading font-bold">Raised</div>
</DemoCard>

Color swatches

ColorSwatch takes a palette token and a label, so the swatches follow the current season and light or dark mode:

Accent
Pink
Blue
Green
Yellow
Red
<ColorSwatch color="pink" label="Pink" />

Inline components

Components work inside a sentence, too. The Highlight component gives the words that matter a highlighter-style underline. It comes in two styles: the default slanted bar, and handDrawn, an uneven marker stroke. Add bold for heavier text:

A highlighter makes the words that matter stand out.

A marker stroke looks human.

A bold one.

A <Highlight>highlighter</Highlight> makes the words stand out.
A <Highlight handDrawn>marker</Highlight> stroke looks human.
A <Highlight handDrawn bold>bold</Highlight> one.

The hand-drawn style needs a line of its own for each highlight, so below 832px wide it falls back to the default bar.

Images

Markdown’s ![]() syntax still works in MDX. When you want more control, use Astro’s <Image /> component. Import it from astro:assets, import the image file, and pass the file as src:

import { Image } from 'astro:assets';
import blackCat from '@/assets/blog/mdx-features/black-cat.jpg';

<Image src={blackCat} alt="A black cat with yellow eyes sitting on a wooden bench" width={800} class="my-8" />

Here is the result:

A black cat with yellow eyes sitting on a wooden bench

Photo by Kristin O Karlsen on Unsplash.

Like Markdown images, <Image /> optimizes the file at build time and adds the site’s base path. It also sets the image’s dimensions so the page doesn’t jump while it loads. Props such as width, format, and quality let you control the output, and class styles the <img>. As with Markdown images, keep the file in src/assets/ so Astro can process it.

Define your own components

You aren’t limited to the components shown here. Create your own Astro components and import them into a post the same way. We recommend keeping them in src/components/posts/, next to DemoCard and ColorSwatch.

Here’s a small one, Callout. As an Astro component, it takes a title prop and renders whatever you put inside it through a <slot />:

---
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<aside class="my-8 border-2 border-l-8 border-ink border-l-accent bg-bg-elevated p-5">
  <div class="font-heading font-bold">{title}</div>
  <div class="text-muted">
    <slot />
  </div>
</aside>

Save it as src/components/posts/Callout.astro and import it like the others.

For something small that only one post needs, you can skip the extra file and define the component right in the post with export. It’s plain JSX, so it receives children instead of a slot. This post does exactly that:

export const Callout = ({ title, children }) => (
  <aside class="my-8 border-2 border-l-8 border-ink border-l-accent bg-bg-elevated p-5">
    <div class="font-heading font-bold">{title}</div>
    <div class="text-muted">{children}</div>
  </aside>
);

<Callout title="Heads up">Callouts are a good place for tips and warnings.</Callout>

An inline component only exists in the post that defines it, so anything you want to reuse belongs in its own .astro file.