Styling code with Theme UI, Gatsby, and MDX
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.jsimport 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.tsexport 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.tsconst 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 linesreturn true}if (content === '// highlight-end') {startEndRangesToHighlight.push(index - 2) // since we're removing start and end lines, we'll shorten the range by 2 linesreturn 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.tsexport default {styles: {pre: {// [...]'.highlight': {backgroundColor: 'highlight',fontWeight: 'bold',},},}}
Nice!