Tasos Tasos - 5 days ago 7
CSS Question

Create line numbers on pre with CSS only

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?


Answer

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>

Comments