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.
| Directive | Description |
|---|---|
::article-list | Sorted article list from a content directory |
::article-list-item | Single article row (used internally by ::article-list) |
::table-of-contents | Scroll-aware heading navigation |
::spacer | Vertical whitespace |
::progress-bar | Progress bar with a fixed value or live date-range math |
:::timeline | Vertical job-experience timeline container |
:::timeline-item | Individual role entry inside a :::timeline |
:::callout | GitHub-style note, tip, warning, or caution box |
- directives/article-listarticle-list
Renders a sorted list of articles from a content directory.
Read - directives/calloutcallout
Renders a GitHub-style callout box with a colored border, tinted background, and an icon — for notes, tips, warnings, and more.
Read - directives/progress-barprogress-bar
Renders a labeled progress bar, either with a fixed value or calculated live from a date range.
Read - directives/spacerspacer
Inserts vertical whitespace between elements.
Read - directives/table-of-contentstable-of-contents
Auto-detects headings and renders a linked, scroll-aware navigation list.
Read - directives/timelinetimeline
Renders a vertical timeline from structured entries with freeform badge metadata.
Read
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 as | Prop received | Example |
|---|---|---|
attr=text | string | dir=guides → dir="guides" |
flag (no value) | true (boolean) | recursive → recursive={true} |
attr=false | false (boolean) | recursive=false → recursive={false} |
attr=5 | number | limit=5 → limit={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:
| Directive | Component |
|---|---|
::spacer | <Spacer /> |
::article-list | <ArticleList /> |
::table-of-contents | <TableOfContents /> |
::progress-bar | <ProgressBar /> |
:::timeline | <Timeline /> |
:::timeline-item | <TimelineItem /> |
:::callout | <Callout /> |
Adding new directives
- Create the underlying component in
src/components/mdx/. - Export it from
src/components/mdx/index.tsxand add it to themdxComponentsmap.
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.