Skip to content

Language Tabs for Markdown & MDX Code Blocks


Created Sep 07, 2019 – Last Updated Apr 23, 2021

GatsbyMDX
Digital Garden

Integrating syntax highlighting in Gatsby is solvable with solutions like gatsby-remark-prismjs or prism-react-renderer. When creating the code block in Markdown you specify the desired language (e.g. js or css) after the opening three backticks. It’s a nice touch to display the specified language also in the code block itself, like I do it on my blog here:

js
const harry = spell("lumos");

The following two quick tips explain how to integrate this feature with gatsby-remark-prismjs or prism-react-renderer (Markdown or MDX respectively).

#Markdown & gatsby-remark-prismjs

I created the codesandbox language-tabs-for-gatsby-remark-prismjs which you can have a look at the final and working code if you prefer to jump straight to the solution.

Set up a Gatsby project which allows you to source data from Markdown. You can e.g. run npm init gatsby and select the Markdown option. Now install gatsby-remark-prismjs and configure your gatsby-config.js & gatsby-browser.js accordingly. Add some dummy content to one of your markdown files to see something happen, e.g.:

md
```js
const harry = spell("lumos");
```

After starting your Gatsby dev server you should have a syntax highlighted code block. Now do a right-click on the code block, followed up by a click on Inspect element to view the block in the developer tools. You see something like:

html
<div class="gatsby-highlight" data-language="js">
<pre
class="language-js"
><code class="language-js">YOUR_CONTENT_HERE</code></pre>
</div>

You need to target the .gatsby-highlight class and pre tag to add the label via the pseudo-selector ::before.

Create a CSS file and include it in your gatsby-browser.js file. The CSS itself is:

custom-prism-styles.css
css
1.gatsby-highlight {
2 position: relative;
3 -webkit-overflow-scrolling: touch;
4}
5.gatsby-highlight pre[class*="language-"] {
6 -webkit-overflow-scrolling: touch;
7}
8.gatsby-highlight pre[class*="language-"]::before {
9 background: black;
10 border-radius: 0 0 0.25rem 0.25rem;
11 color: white;
12 font-size: 12px;
13 letter-spacing: 0.025rem;
14 padding: 0.1rem 0.5rem;
15 position: absolute;
16 right: 1rem;
17 text-align: right;
18 text-transform: uppercase;
19 top: 0;
20}
21.gatsby-highlight pre[class="language-js"]::before {
22 content: "js";
23 background: #f7df1e;
24 color: black;
25}

The wrapping div has a relative position so that the tab itself can be positioned absolutely. The CSS also makes use of attribute selectors to target every pre tag with language-* classes. Every language will be targeted by this (your default styling), hence for specific languages you overwrite some of the styling.

If you want to add tabs for other languages, copy the existing language-js version (you can again inspect the DOM before to know what to target).

#MDX & prism-react-renderer

When using MDX and gatsby-plugin-mdx you could also use the aforementioned gatsby-remark-prismjs, however MDX offers you more possibilities, so why not use them, e.g. for a live editor? Set up a new project with npm init gatsby and choose MDX this time. Also install prism-react-renderer. You can copy custom-prism-styles.css from the first quick tip to this project and also import it inside gatsby-browser.js.

To get the CSS from custom-prism-styles.css to work, you’ll need to think of adding a div with .gatsby-highlight when using prism-react-renderer.

The main part is to create a <Code /> component and mapping it to the pre tag in MDX. The language-tabs-mdx codesandbox shows the complete setup, here’s the code component as a snippet:

src/components/code.js
js
import React from "react";
import Highlight, { defaultProps } from "prism-react-renderer";
const Code = () => (
<Highlight {...defaultProps} code={codeString} language={language}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<div className="gatsby-highlight" data-language={language}>
<pre className={className} style={style}>
{tokens.map((line, i) => (
<div {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span {...getTokenProps({ token, key })} />
))}
</div>
))}
</pre>
</div>
)}
</Highlight>
);
export default Code;

If you now start the development server and inspect the HTML the extra container will be in the DOM. Should work with the above CSS now, right? Not quite yet, one small adjustment needs to be made. As you can see in the DOM, the pre element now has two classes:

html
<pre class="prism-code language-js">YOUR_CONTENT</pre>

Therefore the previous pre[class="language-js"] selector won’t work anymore as it expects only this one class. By changing this to pre[class~="language-js"] you say: When one of the class names is language-js, please do the following.


Want to learn more? Browse my Digital Garden