Navigation

Technologies:

Styling Hugo Code Blocks

Styling code blocks in Hugo get a little crazy. Here’s how I went about styling them.

Types of Code Blocks

Content in Hugo can generate 6ish types of code blocks.

  1. Inline Code

    This style is for simple highlights within some other text.

    You write it like this:

    Inline code is written with `backticks`.
    

    The HTML looks like this:

    <p>Inline code is written with <code>backticks</code>.</p>
    

    And the final output looks like this:

    Inline code is written with backticks.

  2. Unhighlighted Blocks

    Unhighlighted code blocks are the standard three-backticks blocks in normal Markdown.

    You write it like this:

    ```
    this kind of code block is great for ascii art
    ```
    

    The HTML looks like this

    <pre tabindex="0">
      <code>this kind of code block is great for ascii art</code>
    </pre>
    

    And the final output looks like this:

    this kind of code block is great for ascii art
    
  3. Highlighted Blocks: Recognized

    This is where it starts to get a bit messy

    You write it like this:

    ```lua
    print("hello world")
    ```
    

    The HTML looks like this:

    <div class="highlight">
        <pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
            <code class="language-lua" data-lang="lua">
                <span style="display:flex;"><span>print(<span style="color:#b8bb26">"hello world"</span>)</span></span>
            </code>
        </pre>
    </div>
    

    And the final output looks like this:

    print("hello world")
    
  4. Highlighted Blocks: Table Line Numbers

    You write it like this:

    ```lua {linenos=table,hl_lines=[4,"1-2"],linenostart=199}
    function hello_world(name)
        print("Hi "..name)
    end
    
    hello_world("Dave")
    ```
    

    The HTML looks like this:

    <div class="highlight">
      <div style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;">
        <table style="border-spacing:0;padding:0;margin:0;border:0;">
          <tbody>
            <tr>
              <td style="vertical-align:top;padding:0;margin:0;border:0;">
                <pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;">
                  <code>
                    <span style="background-color:#3d3d3d">
                      <span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#756d59">199</span>
                    </span>
                    <span style="background-color:#3d3d3d">
                      <span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#756d59">200</span>
                    </span>
                    <span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#756d59">201</span>
                    <span style="background-color:#3d3d3d">
                      <span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#756d59">202</span>
                    </span>
                    <span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#756d59">203</span>
                  </code>
                </pre>
              </td>
              <td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
                <pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;">
                  <code class="language-lua" data-lang="lua">
                    <span style="display:flex; background-color:#3d3d3d">
                      <span>
                        <span style="color:#fe8019">function</span>
                        <span style="color:#fabd2f">hello_world</span>(name)
                      </span>
                    </span>
                    <span style="display:flex; background-color:#3d3d3d">
                      <span>    print(
                        <span style="color:#b8bb26">"Hi "</span>
                        <span style="color:#fe8019">..</span>name)
                      </span>
                    </span>
                    <span style="display:flex;">
                      <span>
                        <span style="color:#fe8019">end</span>
                      </span>
                    </span>
                    <span style="display:flex; background-color:#3d3d3d">
                      <span></span>
                    </span>
                    <span style="display:flex;">
                      <span>hello_world(
                        <span style="color:#b8bb26">"Dave"</span>)
                      </span>
                    </span>
                  </code>
                </pre>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    

    (There’s a lot going on. the important bits are that the <code> with the data-lang is buried inside the table, there are two sets of <pre><code>…</code></pre> tags, there are now two <div>s before the <table>, and also there’s a <table> now. )

    And the final output looks like this:

    199
    200
    201
    202
    203
    
    function hello_world(name)
        print("Hi "..name)
    end
    
    hello_world("Dave")
    
  5. Highlighted Blocks: Unrecognized

    You write it like this:

    ```thisLanguageDoesNotExist
    Who knows what this is supposed to do.
    ```
    

    The HTML looks like this:

    <pre tabindex="0">
        <code class="language-thisLanguageDoesNotExist" data-lang="thisLanguageDoesNotExist">Who knows what this is supposed to do.</code>
    </pre>
    

    And the final output looks like this:

    Who knows what this is supposed to do.
    

    It’s extremely similar to the Unhighlighted block, but the <code> still has the data-lang attribute

  6. Inline Highlighted Code

    Hugo comes with a shortcode to do syntax highlighting on inline code.

    You write it like this:

    Inline highlighted code → {{< highlight lua "hl_inline=true" >}}if true print("hello world!") end{{< /highlight >}}.
    

    The HTML looks like this:

    <p>Inline highlighted code looks like this → <code class="code-inline language-lua">
        <span style="color:#fe8019">if</span> <span style="color:#fe8019">true</span> print(<span style="color:#b8bb26">"hello world!"</span>) <span style="color:#fe8019">end</span>
    </code>.</p>
    

    And the final output looks like this:

    Inline highlighted code looks like this → if true print("hello world!") end.

    It renders a similar kind of output as the unhighlighted inline code type, just simple set of <code> tags and some <spans> for the actual highlighting. But unlike the other highlighted code blocks, this one does not have the highlight class, or the data-lang attribute.

These are all structurally pretty different. Sometimes the top level is a <div>, sometimes it’s a <pre>, and sometimes you’ll have multiple <pre>s and <div>s within a block.

CSS

Getting a consistent look out of all of these types a few iterations, but I came up with something pretty clean. Here’s a generic version of the CSS I use on this website to style my code blocks. Feel free to copy and modify this to fit your Hugo website.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
:not(pre) > code {
    /* Inline code. Includes highlighted and unhighlighted versions */
    font-family: monospace;
}

:not(div.highlight) pre:not(:has(code > span)):has(code), div.highlight {
    /* The top level for the "block" style code blocks */
    /* 1st part: Unhihlghighted / unrecognized code blocks. */
        /* Filters out inline code, */
        /* and highlighted code (always have spans), */
        /* and pre blocks within highlighted code. */
    /* 2nd part, all highlighted code blocks (except for the inline version). */

    /* Div and pre have different base styles. sync them up here. */
    margin: 1em 0;
    --parent-padding-size: 0.25rem;
    --child-padding-size: 0.25rem;
    padding: var(--parent-padding-size);
    font-size: 0.9rem;

    & > * {
        /* All blocks only have 1 direct child, but it could be a `<code>`, `<pre>`, or a `<div>` */
        display: block;
        overflow-x: auto;
        font-family: monospace;
        padding: var(--child-padding-size);
        margin: 0;

        table pre { margin: 0; }
    }

    /* Bonus effect to display the name of the language. ↓ */
    &:has(code[data-lang]) {
        position: relative;
        margin-top: calc(2ch - var(--parent-padding-size)/2);
        /*          ↑
            Calculates headroom needed for language label.
            So long as both elements use the same font size, we can estimate
            the height of the language label using `ch` or `em`.
        */

        code[data-lang]::before {
            position: absolute;
            font-family: monospace;
            content: attr(data-lang);
            display: inline-block;
            text-align: right;
            font-size: 1em;
            height: 1em;
            top: calc(-1em + 1px);  /* 1em alone is sometimes off by a hair. */
            padding: 0 0.25rem;
            right: var(--parent-padding-size);

            /* If you add a border to your the code blocks, remember to */
            /* set a background color here to interrupt the border. */
        }
    }
}

The syntax highlighting itself comes from Hugo’s Highlighter system, which uses Chroma.

This post was originaly written with Hugo 0.141 in mind.

Future versions of Hugo may generate a different HTML structure, which may break this CSS.

If by chance you want to see what my CSS actually looks like, you can find it here.