Skip to content
Nate Radebaugh
Nate Radebaugh

How to use custom MDX Fenced Code Blocks to supercharge your documentation

May 11, 2020


MDX lets us use React Components in Markdown files using next-generation tooling. Fenced Code Blocks have some unique extensibility points for exposing new functionality from our .mdx files.


From the MDX site,

MDX is an authorable format that lets you seamlessly write JSX in your Markdown documents. You can import components, such as interactive charts or alerts, and embed them within your content. This makes writing long-form content with components a blast 🚀.


What is Markdown?

Markdown lets us write documentation in human-readable and computer-readable formats. Simple formatting is converted to HTML.

A simple Markdown (.md) file may look like this:

md
1#### Hello, _world_!
2
3This is an example of text in Markdown.
4
5- Red
6- Green
7- Blue

Hello, world!

This is an example of text in Markdown.

  • Red
  • Green
  • Blue

What is MDX?

A simple MDX (.mdx) file may look like this:

md
1#### Hello, _world_!
2
3Below is an example of JSX embedded in Markdown.
4
5<div style={{ padding: "20px", backgroundColor: "#B71C1C" }}>
6 <h3>This is JSX</h3>
7</div>

Hello, world!

Below is an example of JSX embedded in Markdown.

This is JSX


MDX can be great, but as you can see, we can lose some of the flexibility and readability markdown has made us believe.

Take the following .mdx:

md
1import BundlephobiaInline from "bundlephobia-inline";
2
3<BundlephobiaInline packageName="react-query" />
4<BundlephobiaInline packageName="react-table" />
5<BundlephobiaInline packageName="react" />
6<BundlephobiaInline packageName="react-dom" />
7<BundlephobiaInline packageName="dayjs" />
8<BundlephobiaInline packageName="styled-components" />

This has turned a simple list of packages into an overly verbose list of code. I'd much rather use something like this

bundlephobia
1react-query
2react-table
3react
4react-dom
5dayjs
6styled-components

Using the <MDXProvider components={components} />, we can customize any code blocks with a custom React component.

jsx
1import { MDXProvider } from "@mdx-js/react";
2
3const components = {
4 pre: (props) => <div {...props} />,
5 code: Code,
6};

Let's take a look at how <Code /> works, and what props get passed.

bundlephobia
react-query
  • children as the content
    • react-query
  • className with language-*
    • language-bundlephobia
  • metastring with

With a little trickery, we can overload the language- value being passed in to short-circuit the Code return value for the unique scenarios:

jsx
1function Code(props) {
2 const { children: codeString = "", metastring = "", className = "" } = props;
3 const language = className.replace(/language-/, "");
4
5 //
6 // If we see "bundlephobia" fenced code block
7 //
8 if (["bundlephobia"].includes(language)) {
9 const lines = codeString.trim().split(/[\n\r]+/);
10 return (
11 <>
12 {lines.map((line) => (
13 <div>
14 <Bundlephobia key={line} packageName={line} />
15 </div>
16 ))}
17 </>
18 );
19 }
20
21 // ...
22}

Here's it running in action:

bundlephobia
1react-query
2react-table
3react
4react-dom
5dayjs
6styled-components

Consider some other ways to exploit this functionality:

Inline Charts

txt
// ```chart type=bar
Green Team=[1,2,3]
Blue Team=[3,4,2]

vs

jsx
1<Chart
2 records={[
3 { label: "Green Team", data: [1, 2, 3] },
4 { label: "Blue Team", data: [3, 4, 2] },
5 ]}
6/>

Inline Maps

txt
// ```map
Groom Lake, Nevada

vs

jsx
<Map address="Groom Lake, Nevada" />

Further reading...