Guides, highlight
Jun 28, 2022
Implement a Dark and Light theme in rehype-highlight
A guide on how to do it
Let an AI answer your questions
What is rehype-highlight?
rehype-highlight
is a plugin for the rehype
HTML processor that provides syntax highlighting for <code>
blocks.
One common way to use this plugin is with Markdown compilers that use rehype
and convert Markdown into HTML or JavaScript. For example, if you use Next.js and the library next-mdx-remote
to load and process Markdown, you will tell your library to use rehype-highlight
like this:
serialize(
// Raw MDX contents as a string
'# hello, world',
// Optional parameters
{
// made available to the arguments of any custom mdx component
scope: {},
// MDX's available options, see the MDX docs for more info.
// https://mdxjs.com/packages/mdx/#compilefile-options
mdxOptions: {
remarkPlugins: [],
rehypePlugins: [rehypeHighlight],
format: 'mdx'
},
// Indicates whether or not to parse the frontmatter from the mdx source
parseFrontmatter: false,
}
)
How to apply CSS syntax themes to rehype-highlight
Independent on the way you use rehype-highlight
, the plugin will apply a class to every element processed. By default they follow the format hljs-
. CSS syntax themes compatible with rehype-highlight
will target these classes and apply the corresponding CSS. An example of a stylesheet like this is atom-one-dark. Here are the first few lines as an example:
.hljs {
color: #abb2bf;
background: #282c34;
}
.hljs-comment,
.hljs-quote {
color: #5c6370;
font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #c678dd;
}
The problem when attempting to use light and dark themes on your page
Loading a stylesheet such as this one works as expected and applies the styles only where it is supposed to. It is compatible with Chakra UI, TailwindCSS or other CSS frameworks and libaries. But what happens if you want to have a different syntax stylesheet depending on if the user selects a light or dark theme? rehype-highlight
does not natively implement such functionality.
One potential solution would be to load a different stylesheet using JavaScript, based on the current color theme applied. This solution is not very elegant, forces a stylesheet reload every time the user changes the color theme and is problematic to implement in Next.js: stylesheets should be loaded from _app
(if loading a local CSS file) or _document
(if using <link>
) and these files should not contain this type of logic (Chakra's useColorModeValue hook cannot be executed from these files, for example).
The solution
My solution is to load both stylesheets and modify the selectors so that each stylesheet only targets elements that have a parent with a specific class (for example, .light
and .dark
. Then this class is set programatically based on the current color theme.
First, transform the CSS. Using the previous CSS as an example:
.dark .hljs {
color: #abb2bf;
background: #282c34;
}
.dark .hljs-comment,
.dark .hljs-quote {
color: #5c6370;
font-style: italic;
}
.dark .hljs-doctag,
.dark .hljs-keyword,
.dark .hljs-formula {
color: #c678dd;
}
You can also use SCSS, which will singnificantly simplify selectors.
Lastly, create a parent component and programatically set its class based on the current color theme.
In the following example, I am using ChakraUI, which provides the hook useColorModeValue to return a different value depending on the selected color theme. Using said value I can then add a class programatically:
const PostPage = () => {
const codeStyleClass = useColorModeValue('light', 'dark')
return (
<Box className={codeStyleClass}>
<Post />
</Box>
)
// Box is a Chakra component that renders a `<div>`, and Post is a custom cumponent that includes the markdown processor output
Summary
By including a parent component that sets its class programatically based on the current color theme and by modifying the syntax stylesheet to target only components that have a parent with that class, we can implement a light and dark theme that applies to syntax highlighting using rehype-highlight
.