Styling code with Theme UI, Gatsby, and MDX

Entry 05 Dec 27, 2020 02 min read

The time had come to style code blocks in my blog. And in the spirit of avoiding premature feature optimization, I promised myself I’d start with just the basics:

  • Style inline <code/> blocks.
  • Style code fences with language-appropriate syntax highlighting.
  • Make everything look nice with my three existing color modes.

Fancier features like code highlighting individual lines and copy-to-clipboard buttons would have to wait for another day (… or would they?). After punting most “nice-to-haves”, I churned out an MVP in a couple hours. This included a few minutes of setup, with the rest of my time spent tinkering with color palettes. 🎨

I even had time to figure out how to highlight individual lines of code!

To start, I installed @theme-ui/prism:

npm i @theme-ui/prism

This package includes prism-react-renderer, so I didn’t have to install that separately. I also didn’t install gatsby-remark-prismjs since I’m using MDX.

Since I was using gatsby-plugin-theme-ui, I followed the instructions to shadow the components.js file:

// src/gatsby-plugin-theme-ui/components.js
import Prism from '@theme-ui/prism';
export default {
pre: props => props.children,
code: Prism,
}

That was mostly it. And it only took a couple minutes — not bad!

But the actual keyword styling in my code blocks looked a bit underwhelming (lots of plain text colors everywhere). So I re-visited some overlooked color keys in my theme.colors object. This meant manually choosing a color for text, background, highlight, gray, primary, secondary, muted, and accent for each of my three color modes. I borrowed liberally from existing theme-ui/presets, but this still took a good hour of playing around with color combinations.

I also spruced up my theme.styles object a bit. I’m not sure if this is the best way to apply custom <pre /> styles, but hey it worked!

// gatsby-plugin-theme-ui/index.ts
export default {
styles: {
pre: {
...prism,
fontFamily: 'Menlo, monospace',
lineHeight: 1.5,
fontSize: '14px',
fontWeight: 400,
padding: '1rem',
border: '1px solid',
maxWidth: '768px',
backgroundColor: 'muted',
overflowY: 'scroll',
my: 6,
},
}
}

I had to nest styles for inline <code/> blocks separately, which really felt like not the best way to do this, but whatever.

// gatsby-plugin-theme-ui/index.ts
const inlineCodeStyles = {
fontFamily: 'Menlo, monospace',
fontSize: '16px',
backgroundColor: 'muted',
color: 'secondary',
lineHeight: 1.6,
};
export default {
styles: {
p: {
fontSize: 18,
fontFamily: 'serif',
lineHeight: 1.6,
mb: 5,
maxWidth: 700,
// I'd have to repeat this everywhere <code/> appears, like in <ul/>, <ol/>, etc.
code: {
...inlineCodeStyles,
},
},
}
}

I was pretty much done by this point … except I really wanted to implement the ability to highlight individual lines of code. I know, I know — there wasn’t an immediate need for this, and I promised myself that it wouldn’t be part of the MVP. But what’s the point of spinning up your own site if you can’t do whatever you want?

Most code highlighting implementations I found on the internet involved creating a custom component to <MDXProvider/>, wrapping your entire layout with it, and doing a lot of iterating over blocks and other code stuff. This seemed swell and all, but it also seemed like a lot of work. It was also unclear how this would fit into Theme UI’s Prism implementation. Maybe code highlighting wasn’t in store for the MVP after all.

But while perusing the @theme-ui/prism source code, I couldn’t help but notice that the package seemed to support code highlighting out-of-the-box!

// oh hello what's this??
if (content === '// highlight-start') {
startEndRangesToHighlight.push(index) // track our highlighted lines
return true
}
if (content === '// highlight-end') {
startEndRangesToHighlight.push(index - 2) // since we're removing start and end lines, we'll shorten the range by 2 lines
return true
}

It seemed like if I wrapped my code with // highlight-start and // highlight-end comments, @theme-ui/prism would automatically inject a highlight classname into any intermediary token blocks and remove the comments. You can also highlight a single line with a // highlight-line comment placed at the end of the line.

This code highlighting feature wasn’t available on the latest stable version I had installed (^0.3.5), so I had to upgrade @theme-ui/prism, gatsby-plugin-theme-ui, and theme-ui to ^0.4.0-alpha.3 (these might actually be on a rc version as of writing). Using alpha versions of various npm packages for a minor feature that I didn’t really need … what could possibly go wrong? 🙃

I was somewhat comforted by the fact that Gatsby installed these versions for one of their newer themes. If it’s good enough for the Gatsby core team, it’s good enough for me! (Update: This feature is under active development, so proceed with caution)

So with the (hopefully stable-enough) versions of everything installed, I just had to style the newly highlighted token blocks in my styles.pre object:

// src/gatsby-plugin-theme-ui/index.ts
export default {
styles: {
pre: {
// [...]
'.highlight': {
backgroundColor: 'highlight',
fontWeight: 'bold',
},
},
}
}

Nice!

Vanilla Three.js to React Three Fiber