CSS, auto numbering elements : headings, lists, pagination

Logo

Introduction

Auto numbering headings using Word, OpenOffice, LibreOffice… is easy and well known by users since decades.

How to achieve it when publishing HTML pages ?

CSS is powerful, CSS can do the job. Always investigate CSS features before starting a development (Javascript…), often CSS is able to cover very easily a need.

To autonumber elements :

  • 3 CSS properties applied mainly on the pseudo class ::before : content, counter-increment, counter-reset.
  • 2 CSS functions : counter(), counters().

That’s all.

<tag>::before {
  content: counter(…);
  counter-increment: …;
  counter-reset: …;
}

Numbering headings

How autonumbering headings is performed in this paper ?

Autonumbering headings is applied in this paper using only CSS. All headings tags to be numbered are encapsulated in the main tag here, obviously it may be another parent tag (body, div…):

<main>
    <h2>Introduction</h2>
    <h2>Numbering headings</h2>
        <h3>How autonumbering headings is performed in this paper ?</h3>
        <h3>Disabling autonumbering</h3>
        <h3>Changing format</h3>
    <h2>Numbering lists</h2>
    <h2>Pagination</h2>
    <h2>Conclusion</h2>
</main>

<h1> tag is not numbered. Usually there is only one <h1> tag in a page and it is the title.

A counter is defined for each <hx> tag and numbering is applied until <h4> (it is recommended to set a reasonable depth level for headings tags).

<h2>h2counter
<h3>h3counter
<h4>h4counter

Sub headings counters are reset in the counter-reset property of the parent heading tag:

main    { counter-reset: h2counter; }
main h2 { counter-reset: h3counter; }
main h3 { counter-reset: h4counter; }

In the ::before pseudo class for <hx> tags, counter is incremented and numbering content is defined :

main    { counter-reset: h2counter; }
main h2 { counter-reset: h3counter; }
main h3 { counter-reset: h4counter; }

main h2::before {
    counter-increment: h2counter;
    content: counter(h2counter) ".";
}
main h3::before {
    counter-increment: h3counter;
    content: counter(h2counter) "." counter(h3counter) ".";
}
main h4::before {
    counter-increment: h4counter;
    content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) ".";
}

That’s all.

Disabling autonumbering

For some headings (introduction, conclusion, appendix…), disabling autonumbering is preferred.

Define a custom attribute, for example data-nocount :

<h2 data-nocount>Introduction</h2>

and apply numbering only if the tag does not contain this attribute :

main                        { counter-reset: h2counter; }
main h2:not([data-nocount]) { counter-reset: h3counter; }
main h3:not([data-nocount]) { counter-reset: h4counter; }

main h2:not([data-nocount])::before {
    counter-increment: h2counter;
    content: counter(h2counter) ".";
}
main h3:not([data-nocount])::before {
    counter-increment: h3counter;
    content: counter(h2counter) "." counter(h3counter) ".";
}
main h4:not([data-nocount])::before {
    counter-increment: h4counter;
    content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) ".";
}

Changing format

Numbering format may be different in pages, examples : 1.1.1., 1-1-1-.

A separator is then defined using a variable, more customizable :

:root {
    --sep-num : '-';
}

main                        { counter-reset: h2counter; }
main h2:not([data-nocount]) { counter-reset: h3counter; }
main h3:not([data-nocount]) { counter-reset: h4counter; }

main h2:not([data-nocount])::before {
    counter-increment: h2counter;
    content: counter(h2counter) var(--sep-num);
}
main h3:not([data-nocount])::before {
    counter-increment: h3counter;
    content: counter(h2counter) var(--sep-num) counter(h3counter) var(--sep-num);
}
main h4:not([data-nocount])::before {
    counter-increment: h4counter;
    content: counter(h2counter) var(--sep-num) counter(h3counter) var(--sep-num) counter(h4counter) var(--sep-num);
}

To change then the numbering separator for a page, just need to redefine the variable in this page after CSS containing numbering rules are loaded :

<link href="./css/style.css" rel="stylesheet">
<style>:root { --sep-num : '.'; }</style>

A variable is also very useful if the numbering format is language dependent. In the below example, the default separator is modified if the document is in French :

:root {
    --sep-num : '.';
}

:lang(fr) { --sep-num : '-'; }

Numbering can be set to other types than numeric : alphabetic, roman, greek, katakana… (Mozilla - list-style-type). The type is the second optional argument of the counter function :

content: counter(h2counter, upper-roman) var(--sep-num);

Numbering lists

<ol> tag is useful for numbering items in a list, unfortunately the numbering is not the expected one when <ol> tags are nested :

  1. Introduction
  2. Section
    1. Subsection
    2. Subsection
  3. Conclusion

Another issue, we may want to apply autonumbering on <ul> items built by a third party library (javascript…) : TocBot for example, a javascript library for Tables of contents dynamic generation.

CSS counters come to the rescue !

In the following use case, the table of contents is generated in a div element (class js-toc):

<div class="js-toc">
	<ul>
		<li><a href="#…">Introduction</a></li>
		<li><a href="#…">Numbering headings
			<ul>
				<li><a href="#…">How autonumbering headings is performed in this paper ?</a></li>
				<li><a href="#…">Disabling autonumbering</a></li>
				<li><a href="#…">Changing format</a></li>
			</ul>
		</li>
		<li><a href="#…">Numbering lists</a></li>
		<li><a href="#…">Pagination</a></li>
    <li><a href="#…">Conclusion</a></li>
	</ul>
</div>
Autonumbering UL lists

To achieve numbering, a counter licounter is reinitialized each time a block ul is created in the table of contents container (<div class="js-toc">) :

div[class*="js-toc"] ul {
  list-style-type: none;
  counter-reset: licounter 0;
}

For each li block, the counter is incremented and the counters function is used to fill the content of the ::before pseudo-class :

:root {
    --sep-num : '.';
}

:lang(fr) { --sep-num : '-'; }

div[class*="js-toc"] ul {
  list-style-type: none;
  counter-reset: licounter 0;
}

div[class*="js-toc"] ul li::before {
	  counter-increment: licounter;
	  content: counters(licounter, var(--sep-num)) var(--sep-num);
      padding-right: 8px;
}

The CSS counters function enables nested counters and returns a concatenated string representing the current values of the named counters.

To disable autonumbering, like headings, a user attribute data-nocount is defined :

<li data-nocount><a href="#…">Introduction</a></li>

and the numbering is applied only if the li tag does not contain the attribute data-nocount :

div[class*="js-toc"] ul li:not([data-nocount])::before {
	  counter-increment: licounter;
	  content: counters(licounter, var(--sep-num)) var(--sep-num);
      padding-right: 8px;
}

The third optional argument of the counters function changes the numbering type :

content: counters(licounter, var(--sep-num), upper-roman) var(--sep-num);

Pagination

Knowing this powerful feature, pagination toolbars are easily built without having to compute and print the counters :

ul[class*="paging"] {
    list-style-type: none;
    counter-reset: pgcounter var(--start-toolbar-paging);
}
ul[class*="paging"] li a:not([data-nocount])::before {
	  counter-increment: pgcounter;
	  content: counter(pgcounter);
}

Unfortunately, the --start-toolbar-paging variable must be defined before in the page. It is currently forbidden to use attribute values which are string data types to define integer counters, the new CSS version levels (CSS 4 ?) will probably cover this long awaited feature.

ul[class*="paging"] {
    list-style-type: none;
    counter-reset: pgcounter attr(data-count-start);
}

Conclusion

A conclusion ? Consult the CSS documentations and stay up to date : powerful, CSS can save hours of programming and headhaches.