Adding adaptive syntax highlighting to your Hugo blog's dark mode
Context
Adding a dark mode to your Hugo blog is a good way to appeal to your readers. Many Hugo themes already implement this functionality (I use paper). But even adding it manually is simple enough. However, if you use code snippets with syntax highlighting in your posts your style / theme will likely only match either light or dark mode. Imagine having a relaxed read on data-dive.com using dark-mode just to have your eyes strained by something like this:
Not in my house! I respect your eyesight, so I want it to look like this:
The solution? We make the syntax highlighting style adaptive. We use a theme suited for light-mode and one for dark-mode. To do so, we only need a bit of JavaScript and some quick edits to the Hugo config.
Instructions
The recent versions of Hugo uses chroma for syntax highlighting. So, first decide on the styles you want to use. I like monokailight
for light-mode and onedark
for dark-mode. Next, create the corresponding .css
files to be used by hugo:
hugo gen chromastyles --style=monokailight > syntax_light.css
hugo gen chromastyles --style=onedark > syntax_dark.css
Move those to your theme’s assets
folder (e.g. ./themes/paper/assets/
).
Following, make sure that our Hugo theme loads those styles. For this, edit ./themes/paper/layout/partials/head.html
(or whichever file is responsible for adding a head part in your theme) and add this:
{{ $syntax_dark_css := resources.Get "syntax_dark.css" | minify }}
{{ $syntax_light_css := resources.Get "syntax_light.css" | minify }}
<link rel="preload stylesheet" as="style" href="{{ $syntax_dark_css.Permalink }}" />
<link rel="preload stylesheet" as="style" href="{{ $syntax_light_css.Permalink }}" />
This will make the .css
files available when building the site with Hugo and allow our page to load them. The order is important: the style in the last referenced file is applied by default, as it overrides the former! To make Hugo use our .css
files as chroma styles, we need to explicitly state the following in hugo’s config.toml
:
[markup.highlight]
noClasses = false
To assure you’re on track, check that your code is now highlighted with the light style. When you switch to dark-mode, nothing will happen just yet.
How to change this? Well, we need to dynamically switch which of the .css
files is applied to our code snippets. We can achieve this using some JavaScript.
In ./themes/papers/partials/header.html
(or whichever file is responsible for adding the switch between modes in your Hugo theme), we look for the code that toggles modes. In my case, it looks like this:
const setDark = (isDark) => {
metaTheme.setAttribute('content', isDark ? '#000' : lightBg);
htmlClass[isDark ? 'add' : 'remove']('dark');
localStorage.setItem('dark', isDark);
};
We add a single function call to this (we’ll write the function next):
const setDark = (isDark) => {
metaTheme.setAttribute('content', isDark ? '#000' : lightBg);
htmlClass[isDark ? 'add' : 'remove']('dark');
localStorage.setItem('dark', isDark);
setSyntaxDark(isDark);
};
Now, let’s define the setSyntaxDark
function and an additional helper function:
function getStyleSheet(file_name) {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href.includes(file_name)) {
return sheet;
}
}
}
function setSyntaxDark(isDark) {
sheet_light = getStyleSheet("syntax_light")
sheet_dark = getStyleSheet("syntax_dark")
sheet_light.disabled = isDark ? true : false
sheet_dark.disabled = isDark ? false : true
}
That’s really the heart of our solution. So, let’s take a second to discuss what’s going on. The getStyleSheet
function iterates over all style sheets loaded on the current page. For each sheet, it checks if the url used in href
includes the file_name
. If it does, it returns a reference to that style sheet. We use this function in setSyntaxDark
to get a reference to both our style sheets. There, we also set the disabled
property to deactivate one sheet while activating (=applying) the other. This results in toggling between the chroma style used to display the syntax highlighting. So, whenever the dark-mode is activated the syntax_dark.css
is the style sheet applied and the other way around.
Subsequently, let Hugo build your site and make sure everything works as expected. If you’re having any issues, the dev-mode console is probably the best place to start debugging.
Finally, deploy your site, grab a drink and rest assured that you vastly improved your readers’ dark-mode experience!