CSS (Sass) for Content-Agnostic Numbered Headings
You’re writing a blog post or other HTML document and you want to number your h2
and h3
elements to give users a sense of where they are in the page’s logical flow. Or perhaps you’re maintaining legal content or technical documentation that needs to have numbered h2
, h3
, etc elements. I suppose you could nest the content of your entire document in a bunch of nested lists, or you could write the outline-style numbering by hand. Or … perhaps there’s a better way that only requires CSS.
We’ll start by checkout the code that makes this happen. What you see below is a Sass mixin, but you can do this with just the CSS output if you like.
@mixin generate-outline($reset-element: body, $list-style: decimal) {
$counter: unique-id();
counter-increment: $counter;
&::before {
content: counter($counter, $list-style)'. ';
}
@at-root {
#{$reset-element} {
counter-reset: $counter;
}
}
}
// How to use it
h2 {
@include generate-outline(h1, upper-alpha);
}
h3 {
@include generate-outline(h2);
}
Setting Up Defaults #
You’ll use the generate-outline()
mixin on each selector you want to number. The mixin takes two optional arguments: $reset-element
and $list-style
. $reset-element
is the selector that you want to restart your numbering at. For example, if you want to number your h3
elements, but restart that count everytime your markup includes an h2
, you would call @include generate-outline(h2);
in your h3
declaration block. If you leave $reset-element
empty, the mixin will default to body
.
The second argument is $list-style
and should be a valid value for the CSS list-style-type
property. If you leave it blank, $list-style
will default to decimal
.
Using CSS Counters #
The Sass mixin generate-outline()
automates the use of CSS counters. There are 3 CSS properties we need to use to see counters in action.
Each level of counter needs a unique name. In the Sass above, we’re creating that name by using the Sass unique-id()
function. If you’re just using CSS, you’ll need to choose a different name for each level of numbering.
We can increment that counter by using counter-increment: $name;
on the element that’ll show the counter’s number.
We’ll use the ::before
pseudoelement and the CSS content
property to display that counter on each heading: content: counter($name)'. ';
. Notice that we’re putting the string '. '
on the end of each number. This makes sure we get “A. Heading” instead of “AHeading.”
For the 2nd level of numbered headings, we’ll need to reset that counter every time an element in the 1st level of headings goes by. The Sass mixin gets the position of the current level in the $headings
map - if it’s a 2nd (or deeper) level counter, the mixin will get the next element above it, and use that element to reset the counter. If the current element is the first element in the loop, the mixin will just attach counter-reset: $name;
to the body
element.
Output: Numbered Headings #
With the CSS we’ve just generated, we’ve attached an A, B, C numbering system to all h2
elements in the page, and a 1, 2, 3 numbering system to all h3
elements in the page. This system doesn’t care what else you’ve got in your page: any paragraphs, blockquotes, other heading levels, images, etc – none of that affects this CSS’s ability to number h2
and h3
elements sequentially.
You can see this code in action on this CodePen.
See the Pen Sass / CSS for Numbered Document Headings by James Steinbach (@jdsteinbach) on CodePen.