Reputation: 2448
I want headings in a simple HTML web page to be hierarchically numbered. I thus have:
body {
counter-reset: h1counter;
}
h1:before {
content: counter(h1counter) ".\0000a0\0000a0";
counter-increment: h1counter;
counter-reset: h2counter;
}
h2:before {
content: counter(h1counter) "." counter(h2counter) ".\0000a0\0000a0";
counter-increment: h2counter;
}
<h1>Section 1</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 2</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 3</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
But this displays as follows:
1. Section 1
1.1. Subsection 1
1.1. Subsection 2
2. Section 2
2.1. Subsection 1
2.1. Subsection 2
Section 3
3.1. Subsection 1
3.1. Subsection 2
Obviously, the second numbering digit of the Subsection 2 <h2>
headings should be 2, but it doesn't increment (as if counter-increment: h2counter;
wasn't executed), like it does for the <h1>
headings.
What did I miss?
Upvotes: 2
Views: 161
Reputation: 11273
Until very recently I had the same misunderstanding of CSS counter "scoping" implementations and ran into similar issues. After consulting specs it turned out that implementations, while quite intuitive in effect, mostly did not match specifications: there is no "global" scope for counters.
The scope of a counter therefore starts at the first element in the document that instantiates that counter and includes the element’s descendants and its following siblings with their descendants. However, it does not include any elements in the scope of a counter with the same name created by a counter-reset on a later sibling of the element, allowing such explicit counter instantiations to obscure those earlier siblings.
-- (archived) CSS Lists and Counters Module Level 3: 4.3. Nested Counters and Scope
Actually counter-reset
is a bit misleading name for what it currently does: it really initializes counter of given name in enclosing DOM scope of given element and it's next siblings. Setting it for pseudo element creates distinct scope at the host element only:
h1::before { counter-reset: x; }`
creates x
counter for all h1
descendants, effectively similar to
h1 > *:only-child { counter-reset: x; }`
So answer for your question is logically: move the counter reset out of pseudo element. If you are OK with implicit scope (closest to element and spread on its siblings), you don't even need the explicit reset.
h1 {
counter-increment: h1counter;
counter-reset: h2counter;
}
h1::before {
content: counter(h1counter) ".\0000a0\0000a0";
}
h2::before {
counter-increment: h2counter;
content: counter(h1counter) "." counter(h2counter) ".\0000a0\0000a0";
}
<h1>Section 1</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 2</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 3</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
I'll put some explorative playground for this topic. I confess even now I'm not really confident about all nuances. And note that there are still differences in implantations: Firefox seems currently closer to the specs than Chrome.
<button aria-pressed="false">
reset-on-body
<style media="none">
body { counter-reset: list-item; }
</style>
</button>
<div>
<button aria-pressed="false">
reset on div:
<style media="none">
div { counter-reset: list-item 11; }
</style>
</button>
<p>
<button aria-pressed="false">
reset to 11:
<style media="none">
div > p { counter-reset: list-item 11; }
</style>
</button>
<button aria-pressed="false">
set to 12:
<style media="none">
div > p { counter-set: list-item 12; }
</style>
</button>
</p>
</div>
<p>P before OL</p>
<ol>
<li>
<button aria-pressed="false">
Disable native ol reset:
<style media="none">
ol { counter-reset: none; }
</style>
</button> OL
<li>bar
<ol>
<li>baz
<li>gazonk
</ol>
<li>qux
</ol>
<p>P next to OL</p>
<script>
[...document.querySelectorAll('button')].forEach(b=>{
var a = 'aria-pressed'
b.onclick = function() {
var to = this.getAttribute(a) !== 'true';
this.setAttribute(a,String(to));
this.firstElementChild.setAttribute('media',to?'all':'none')
}
})
</script>
<style>
:root { background: dimgray; color: snow; }
:link { color: aqua; } :visited { color: lime; }
body, div, p, ol {
padding: .2em 2em;
outline: 1px dashed;
}
p::after {
content: ' // list-item: ' counter(list-item);
display: block;
}
style[media] {
display: block; font-family: monospace;
margin-bottom: .3em;
}
[aria-pressed="false"]::before { content: '☐ ' }
[aria-pressed="true"]::before { content: '☒ ' }
[aria-pressed="true"] { outline: solid; }
::marker, p::after { outline: solid; }
</style>
Upvotes: 0
Reputation: 67
HTML
<h1>Section 1</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 2</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 3</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
CSS
body {counter-reset:section;}
h1 {counter-reset:subsection;}
h1:before
{
counter-increment:section;
content:"Section " counter(section) ". ";
font-weight:bold;
}
h2:before
{
counter-increment:subsection;
content:counter(section) "." counter(subsection) " ";
}
Upvotes: 0
Reputation: 110
You should move the "h2counter reset" line in a single h1 selector. The final result for the style should be like this:
body {
counter-reset: h1counter;
}
h1 {
counter-reset: h2counter;
}
h1:before {
content: counter(h1counter) ".\0000a0\0000a0";
counter-increment: h1counter;
}
h2:before {
content: counter(h1counter) "." counter(h2counter) ".\0000a0\0000a0";
counter-increment: h2counter;
}
<h1>Section 1</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 2</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
<h1>Section 3</h1>
<h2>Subsection 1</h2>
<h2>Subsection 2</h2>
Upvotes: 6