home
NOTE: Under Construction - I'm in the middle of upgrading my site and lots of stuff is kinda broken. Please forgive the mess.

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

October 2023

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:

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

I update the HTML to:

Code
<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:

: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()

Details

  • line numbers are added using :before on the .line-num-example 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 outline: 1px solid red; on .line-num-example:before can help figure out where things are)

  • calling counter-reset: line; ensures that line numbers start over if multiple code blocks are on the page

  • overflow-wrap: break-word; and whtie-space: pre-wrap; 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