Skip to content

Language tabs for Gatsby's code blocks

07/09/20193 min readCategory: Quick tip

Integrating syntax highlighting in Gatsby is fairly easy as solutions like gatsby-remark-prismjs or prism-react-renderer exist. 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:

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). If you want to know in addition to that how you could do this with a CMS and CSS-in-JS solution, have a look at the source code of my portfolio.

#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. follow the recipe "Sourcing Markdown data for blog posts and pages with GraphQL" from the Gatsby docs. 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.:

```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 should see something like:

<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 to your page that contains the code. The CSS itself should be:

.gatsby-highlight {
  position: relative;
  -webkit-overflow-scrolling: touch;
}
.gatsby-highlight pre[class*="language-"] {
  -webkit-overflow-scrolling: touch;
}
.gatsby-highlight pre[class*="language-"]::before {
  background: black;
  border-radius: 0 0 0.25rem 0.25rem;
  color: white;
  font-size: 12px;
  letter-spacing: 0.025rem;
  padding: 0.1rem 0.5rem;
  position: absolute;
  right: 1rem;
  text-align: right;
  text-transform: uppercase;
  top: 0;
}
.gatsby-highlight pre[class="language-js"]::before {
  content: "js";
  background: #f7df1e;
  color: black;
}

The wrapping div should have 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 prismjs remark plugin, however MDX offers you more possibilities, so why not use them, e.g. for a live editor? The codesandbox language-tabs-mdx contains the final code, it's also a great place to understand how to implement prism-react-renderer in the first place. You can also read the blogpost "Codeblocks, MDX, and mdx-utils" by Chris Biscardi to learn how to do it.

When following this or other guides on the internet, they insert the highlighted code directly as pre tag:

<Highlight
  {...defaultProps}
  code={codeString}
  language={language}
>
  {({ className, style, tokens, getLineProps, getTokenProps }) => (
    <pre className={className} style={style}>
      {tokens.map((line, i) => (
        <div {...getLineProps({ line, key: i })}>
          {line.map((token, key) => (
            <span
              {...getTokenProps({ token, key })}
            />
           ))}
         </div>
      ))}
    </pre>
  )}
</Highlight>

In order to get the CSS that you wrote in the first quick top to work, you'll need to add a wrapper div.

<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>

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:

<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.

Tagged with
All Tags

Sparked your interest? Read all posts in the category Quick tip

More posts