Home
Head's Up: I'm in the middle of upgrading my site. Most things are in place, but there are something missing and/or broken including image alt text. Please bear with me while I'm getting things fixed.

Add Line Numbers To Code Blocks Using Only CSS (And Some Spans)

Introduction

I'm using a rust crate called syntect 1 to highlight code in my site builder 2 . It works great, but it doesn't add line numbers. This post shows how I'm adding them with CSS.

Updating The Output

The technique I'm using to display line numbers works by adding an empty span to the start of each line. For example, instead of :

html
<div class="code-wrapper-example">
<pre><code>alfa
bravo
charlie
delta</code></pre>
</div>

I update the HTML to :

html
<div class="code-wrapper-example">
<pre><code><span class="line-num-example"></span>alfa
<span class="line-num-example"></span>bravo
<span class="line-num-example"></span>charlie
<span class="line-num-example"></span>delta</code></pre>
</div>

I'm doing this in syntect 3 , but the same technique will work with other tools.

The CSS

With the spans in place, this is the CSS I'm using to add the line numbers :

CSS

:root {
    --code-wrapper-example-bg: midnightblue;
    --code-wrapper-example-pre-bg: black;
    --line-number-example-color: bisque;
}

.code-wrapper-example {
    background-color: var(--code-wrapper-example-bg);
    border-radius: 0.4rem;
}

.code-wrapper-example pre {
    background-color: var(--code-wrapper-example-pre-bg);
    border-top-right-radius: 0.4rem;
    border-bottom-right-radius: 0.4rem;
    counter-reset: line;
    font-size: 0.8rem;
    margin-left: 5.7ch;
    overflow-wrap: break-word;
    padding-block: 1.3ch;
    padding-inline: 2ch;
    white-space: pre-wrap; 
}

.line-num-example {
    counter-increment: line;
}

.line-num-example:before {
    color: var(--line-number-example-color);
    content: counter(line);
    display: inline-block;
    margin-left: -7.5ch;
    padding-right: 2.7ch;
    text-align: end;
    width: 7.5ch;
}

Example Output

Here's an example of what it looks like :

#!/usr/bin/env python3

names = ['Alfa', 'Bravo', 'Charlie', 'Delta', 
    'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 
    'Juliet', 'Kilo', 'Lima', 'Mike', 
    'November', 'Oscar', 'Papa', 'Quebec', 
    'Romeo', 'Sierra', 'Tango', 'Uniform', 
    'Victor', 'Whiskey', 'Xray', 'Yankee', 
	'Zulu']

for name in names:
	print(f"- <<link|{name.lower()[0]}.alanwsmith.com|https://{name.lower()[0]}.alanwsmith.com>>")
	print()
  • line numbers are added using [TODO: Code shorthand span ] on the [TODO: Code shorthand span ] span elements with a CSS counter.

  • line numbers are aligned to the right

  • line numbers can be up to three digits with these settings and my current font. You'll need to fiddle with the various margin and paddings if you have different requirements or a different font. (Putting [TODO: Code shorthand span ] on [TODO: Code shorthand span ] can help figure out where things are)

  • calling [TODO: Code shorthand span ] ensures that line numbers start over if multiple code blocks are on the page

  • [TODO: Code shorthand span ] and [TODO: Code shorthand span ] wrap the text

  • Line numbers are skipped properly when the length of a line of text makes it break to multiple lines

Wrapping Up

I used to use Prism 4 for syntax highlighting. It can display line numbers, but I'm working to cut down on the number of client side dependencies for my site. By generating the syntax highlighted HTML source when the site is built I can drop the javascript and do everything with CSS.

Footnotes And References

  • 1
    syntect

    The rust syntax highlighting crate

  • 2

    I'm making a website builder. It's called Neopoligen and the goal is it to make it easy to build personal websites without having to learn a lot of code. You can learn more about it here : Neopoligen website builder

  • 3

    This is how I'm using the rust syntect create to generate the HTML with the empty spans for the line numbers

  • 4
  • Using CSS to add line numbering

    This is the main post I used for reference. My approach is slightly different in that I'm not wrapping the entire line. That's because title : the way syntect work makes that tricky. Instead, I'm using the single empty span at the start of each line.