MDX Directives

Overview of all built-in directives available in content files.

These directives are available in every .mdx file — no import needed. Each has its own page with a full reference and live examples.

DirectiveDescription
::article-listSorted article list from a content directory
::article-list-itemSingle article row (used internally by ::article-list)
::table-of-contentsScroll-aware heading navigation
::spacerVertical whitespace
::progress-barProgress bar with a fixed value or live date-range math
:::timelineVertical job-experience timeline container
:::timeline-itemIndividual role entry inside a :::timeline
:::calloutGitHub-style note, tip, warning, or caution box

Directive syntax

Block-level (leaf) directives use a double-colon prefix:

::directive-name  
::directive-name{attr=value flag}

Attributes follow a simple coercion scheme — no quotes or braces needed for common cases:

Written asProp receivedExample
attr=textstringdir=guidesdir="guides"
flag (no value)true (boolean)recursiverecursive={true}
attr=falsefalse (boolean)recursive=falserecursive={false}
attr=5numberlimit=5limit={5}

Container directive formatting rules

Container directives (:::name:::) span multiple lines. Prettier's proseWrap: always setting treats Markdown prose as reflowable text and will merge those lines back together unless every line inside the block ends with two trailing spaces (a Markdown hard line break):

:::callout{type=tip}  
Two trailing spaces after this opening line, and after the last line of the
body, keep Prettier from collapsing the block.  
:::

This applies to the opening line, every body line, and — because there's nothing after it to force a break — the last body line before the closing :::. It also applies to an empty body: put the two trailing spaces on the opening line itself so Prettier doesn't pull the closing ::: up onto it.

Keep the opening line at 78 characters or fewer (before the two trailing spaces). Longer lines cause Prettier to wrap mid-attribute-string, which breaks the directive parser.

How directives map to components

Each directive name is converted from kebab-case to PascalCase, then looked up in the MDX component map. No additional configuration is needed — a new component registered in mdxComponents is immediately usable as a directive:

DirectiveComponent
::spacer<Spacer />
::article-list<ArticleList />
::table-of-contents<TableOfContents />
::progress-bar<ProgressBar />
:::timeline<Timeline />
:::timeline-item<TimelineItem />
:::callout<Callout />

Adding new directives

  1. Create the underlying component in src/components/mdx/.
  2. Export it from src/components/mdx/index.tsx and add it to the mdxComponents map.

The directive name is derived automatically: kebab-case the component name (e.g. MyWidget::my-widget).

Special directives

::nav-section is not part of mdxComponents. It is parsed at build time by src/lib/nav.ts exclusively from content/_nav.mdx to populate the sidebar. It cannot be used in regular content files. See the Sidebar reference for the full attribute reference.