Create line numbers on pre with CSS only
Asked Answered
R

5

81

I try to style a code pre box with line numbers in front of each line. I prefer to do it with CSS only. This is what I have done

pre {
  background: #303030;
  color: #f1f1f1;
  padding: 10px 16px;
  border-radius: 2px;
  border-top: 4px solid #00aeef;
  -moz-box-shadow: inset 0 0 10px #000;
  box-shadow: inset 0 0 10px #000;
}
pre span {
  display: block;
  line-height: 1.5rem;
}
pre span:before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  border-right: 1px solid #ddd;
  padding: 0 .5em;
  margin-right: .5em;
  color: #888
}
<pre>
  <span>lorem ipsum</span>
  <span>&gt;&gt; lorem ipsum</span>
  <span>lorem ipsum,\ </span>
  <span>lorem ipsum.</span>
  <span>&gt;&gt; lorem ipsum</span>
  <span>lorem ipsum</span>
  <span>lorem ipsum</span>
  <span>lorem ipsum</span>
</pre>

However, all the lines have the number 1. The increment doesn't work. Here is a jsfiddle

  1. What I am doing wrong?
  2. What is the browser compatibility with this CSS only solution?
Reconstructionism answered 28/11, 2016 at 10:41 Comment(2)
Why not use an OL tag?Palaeozoic
@Graham If I tell you that I totally forgot it, will you believe me? :P :PReconstructionism
A
98

Why does the the counter not increment?

You are not resetting or creating the counter at the parent tag level. If you add the following line to the pre selector, the code will work fine. When you don't create the counter (using a counter-reset) at the parent level, each element would create its own counter and then increment it.

counter-reset: line;

When does a counter get created?

From the W3C Specs:

The counter-reset property creates new counters on an element.

The counter-set and counter-increment properties manipulate the value of existing counters. They only create new counters if there is no counter of the given name on the element yet.

In this case what happens is that we haven't created a counter by using the counter-reset property and so the counter-increment property in the span:before pseudo-element selector would create a counter of the given name and increment it.


How does the counter get to know the current value?

Again from the W3C Specs:

If an element has a previous sibling, it must inherit all of the sibling’s counters. Otherwise, if the element has a parent, it must inherit all of the parent’s counters. Otherwise, the element must have an empty set of counters.

The element then inherits counter values from the immediately preceding element in document order.

Here since the counter is only created in the pseudo-element, its parent (the span) is not aware of its creation and so the siblings of this span doesn't inherit the counter. Since it doesn't even inherit any counter, it doesn't get the current value from the preceding element either.


Why does creating the counter on parent work?

When the counter is created at the pre tag level, the counter is then passed on to each of its children elements (that is, each span and in turn each span:before would know about or inherit this counter) and now the increment statements in the span:before would only increment the value of the counter which it received from the parent.

Now since each span inherits the line counter from its previous sibling, they will also inherit current value from the preceding element in the document order and thus it keeps going up from 1 to 2, 3 etc.


Why does using counter-increment on span or pre work?

As you've guessed, the counter-increment property creates a new counter when there is no existing counter and so adding counter-increment: line on the span will create a counter on the first span that is encountered. Now, since each sibling of the span inherits this counter, it doesn't create a new one every time and rather just inherits the value from the preceding element. Thus this approach will work but it is always better to create the counter explicitly using a counter-reset statement.


How is the browser support?

Browser support for CSS counters is unbelievably good. It is not a new thing in CSS and support for it is available even in IE8.


Demo:

pre {
  background: #303030;
  color: #f1f1f1;
  padding: 10px 16px;
  border-radius: 2px;
  border-top: 4px solid #00aeef;
  -moz-box-shadow: inset 0 0 10px #000;
  box-shadow: inset 0 0 10px #000;
  counter-reset: line;
}
pre span {
  display: block;
  line-height: 1.5rem;
}
pre span:before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  border-right: 1px solid #ddd;
  padding: 0 .5em;
  margin-right: .5em;
  color: #888
}
<pre><span>lorem ipsum</span>
<span>&gt;&gt; lorem ipsum</span>
<span>lorem ipsum,\ </span>
<span>lorem ipsum.</span>
<span>&gt;&gt; lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
</pre>
Anfractuous answered 28/11, 2016 at 10:43 Comment(4)
Nice; I always - mistakenly - thought that we had to define the counter-increment on the non-pseudo element; I hadn't realised at all that using counter-reset on the parent element (wrapping the 'counted-elements') would work as well. The ongoing mysteries of CSS! :)Vizor
@DavidThomas: I've finally completed fleshing it out fully. Pinging you just in case you are interested :)Anfractuous
How to optimize a little bit to make all the bars vertical align better when line numbers have different width?Jackfruit
@MaxPeng: Giving the pseudo-element a width that is wide enough to accommodate all possible numbering values would make them align better (like here). But the thing is you must assign an arbitrary width depending on the max value you expect.Anfractuous
I
4

You have to increment line in your span:

pre span {
    display: block;
    line-height: 1.5rem;
    counter-increment: line;
}

Have a look at this updated jsfiddle.

Izy answered 28/11, 2016 at 10:44 Comment(1)
It works, but when my <span> count is more than 10,000, it takes dozens of seconds.Lampion
M
2

Hi you need to reset the counter on a previous / higher loaded item, check out https://jsfiddle.net/n2xkgt7s/2/ pre { counter-reset: line; }

Mixie answered 28/11, 2016 at 10:49 Comment(0)
Q
2

Instead of <pre> you may consider ordered list <ol>, which already comes with right aligned numbering (nicer for when number of digits jumps, e.g. 9 -> 10). <li> also does not need the closing tag </li> (https://html.spec.whatwg.org/multipage/syntax.html#optional-tags), which saves typing.

ol {
  font-family: monospace;
  white-space: pre; 
}

li::marker {
  font-size: 10px;
  color: grey;
}
<ol><li>lorem ipsum      
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum,\ 
<li>lorem ipsum.
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
<li>lorem ipsum
<li>lorem      
<li>ipsum
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
</ol> 

However, ::marker styling seems limited (list-style-type). E.g. removing the period or vertical-align: super seems to needs other workaround (back to li:before and counter).

Quadruple answered 31/10, 2022 at 21:50 Comment(2)
Thanks - This also solves a problem where I want my users to be able to select the lines of text without selecting the numbers.Effeminize
This is an incomplete solution. Marking up code as an ordered list is inaccessible as it will be presented as a numbered list of items to screenreaders and not a block of code with line numbers.Ewell
F
0

Inspired by this I made a version with support for long lines.

.pre {
    font-size: 16px;
    font-family: monospace;
    background-color: #303030;
    color: #e6be49;
    padding: 15px 20px;
    border-radius: 3px;
    counter-reset: line;
    position: relative;
    line-height: 1.45;
    margin: 0 !important;

    &::after {
        content: "";
        position: absolute;
        top: 15px;
        bottom: 15px;
        left: 40px;
        border-right: 1px solid #5e5e5e;
    }

    span {
        display: block;
        counter-increment: line;
        padding-left: 35px;

        &::before {
            content: counter(line);
            display: inline-block;
            margin-left: -34px;
            margin-right: 25px;
            color: #a0a0a0;
        }
    }
}

Codepen: https://codepen.io/herrfischer/pen/zYXePdR

Freakish answered 24/4 at 16:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.