Notes

Customizing Chroma in Hugo and Light/Dark syntax highlighting themes

Conditionally loading CSS stylesheets in Hugo and loading CSS files as partials is possible.

Edit on GitHub


Hugo
3 minutes

tl;dr

  1. Disable inline styles for syntax highlighting
  2. Generate stylesheets
  3. Use an inline <style> tag in your template file, and load Hugo partials inside a media query (or load stylesheet based on a data attribute value)

Hugo uses Chroma by default. You can use Prism instead if you want.

Chroma renders the highlighted syntax at build time

PrismJS gives you a lot of flexibility in terms of plugins, themes and portability.

PrismJS renders client side whereas Chroma probably renders at build time. Chroma is probably really good for site performance.

My considerations for using one or the other:

  • performance. is it going to download ~50kb on site load? is it going to render on every page load or at build time?
  • portability. can i take my syntax highlighting related changes away with me if i decide to move away from Hugo?
  • flexibility. can i add line numbers and file names? can i add a click to copy button?

PrismJS has a few neat plugins like treeview

I have been using PrismJS for my sites so far. It’s flexible and extendible.

Chroma renders the highlighted syntax at build time, so i decided to switch

1[markup]
2  [markup.highlight]
3    noclasses = false
1hugo gen chromastyles --style=emacs > layouts/partials/css/syntax-light.css
2hugo gen chromastyles --style=monokai > layouts/partials/css/syntax-dark.css

You can see code samples using different themes here

light = xcode, manni, emacs dark = fruity, dracula, native, rrt, vim

comments should be italic and muted color. it should be different from the color of strings and should not be used for anything else all strings should be a different color

1<style type="text/css" media="screen">
2@media (prefers-color-scheme: dark) { 
3  {{ partial "css/syntax-dark.css" . | safeCSS }}
4}
5@media (prefers-color-scheme: light) { 
6  {{ partial "css/syntax-light.css" . | safeCSS }} 
7}
8</style>

Yes, you can load CSS files as partials. They just need to be in the layout/partials folder. Using a partial means that the code is included as inline code inside the HTML.

Alternatively, you can set the mediaattribute directly on <link>as well:

 1<link
 2  rel="stylesheet"
 3  href="/css/syntax-light.css"
 4  media="screen and (prefers-color-scheme: light)"
 5/>
 6<link
 7  rel="stylesheet"
 8  href="/css/syntax-dark.css"
 9  media="screen and (prefers-color-scheme: dark)"
10/>

If your theme changes based on a data attribute, like data-theme="dark" and not based on the prefers-color-scheme media query like @media (prefers-color-scheme: dark) {}, then your code to load the relevant CSS file based on current theme will not work. The issue here is that you need to load both styles from both stylesheets, not one, because the theme can change dynamically any time.

My solution in a scenario like this is to merge your stylesheet into one and use an attribute selector to change my CSS custom properties. See the code below for an example:

 1html[data-theme="dark"] {
 2  --color-syntax-bg: hsl(231.4, 14.9%, 15%);
 3}
 4
 5html[data-theme="default"],
 6html[data-theme="light"] {
 7  --color-syntax-bg: hsl(180deg 11% 95%);
 8}
 9
10/* Background */ .bg { background-color: var(--color-syntax-bg); }
11/* PreWrapper */ .chroma { background-color: var(--color-syntax-bg); }

and then load just a single stylesheet, no conditional loading needed in HTML or JS

1<link rel="stylesheet" href="/css/syntax.css" />