Exhibit 08 of 17
Keeping score
Tick things off. The score under the list is a CSS counter, and the list knows when it’s finished.
The exhibit
How it works
counter-reset on the list starts two counters; every item adds one to total, and every checked item adds one to done as well. The score is written by content: counter(done) " of " counter(total) on an element after the list, so the counting has finished by the time it is printed. :not(:has(input:not(:checked))) reads “no box left unticked”.
The key CSS
Cut from this exhibit’s stylesheet as it is served.
.x-tally .checklist { counter-reset: done total; }
.x-tally .item { counter-increment: total; }
.x-tally .item:has(:checked) { counter-increment: total done; }
/* the score comes after the list, so the counters have finished counting */
.x-tally .score::after { content: counter(done) " of " counter(total) " done"; }
/* no unchecked box left: the list is finished */
.x-tally .checklist:not(:has(input:not(:checked))) .done { display: block; }
Accessibility
The checkboxes are native and fine. The score is generated content: most screen readers read it if you go to it, but nothing announces it when it changes, which a script with a live region would.
Browser support
CSS countersWorks in this browser.Not in this browser: Universal. Checked 2026-10-14: Chromium 149 yes, Firefox 141 yes.:has()Works in this browser.Not in this browser: Without :has() it can’t work; the page says so instead of half-working. Checked 2026-10-14: Chromium 149 yes, Firefox 141 yes.
