Fun Times with CSS Counters

CSS counters are one of those “oh neat, didn’t know CSS could do that” features with a lot of interesting potential. In simple terms, they let you keep a running tally of things in CSS — no JavaScript needed.

Basic Counter

Here’s an easy pagination example to get us started:

See the Pen Pagination CSS Counter by Will Boyd (@lonekorean) on CodePen.

The numbers you see aren’t hardcoded in HTML. They’re generated in CSS with this:

Counter properties follow a “when this element is encountered in the document” flow. The body element is encountered first, initializing a counter named pages. Then the a elements are encountered, each one incrementing and displaying the pages counter.

Multiple Counters

You can have multiple counters just by using different names. This example has two overlapping counters, sections and boxes:

See the Pen Overlapping CSS Counters by Will Boyd (@lonekorean) on CodePen.

Relevant CSS:

Here you can see the syntax for initializing multiple counters at once (line 2). And just to be extra saucy, the boxes counter is displayed as upper-roman (line 18). The full list of display options is the same as the one for list-style-type, documented here.

Counting User Selections

Now we’re getting to the fun stuff. Counter properties can be placed in pseudo-selectors like :checked. This allows counters to react to user selections via checkboxes. Here’s an example that tallies how many selections a user has made:

See the Pen Selection CSS Counter by Will Boyd (@lonekorean) on CodePen.

The CSS is really not much of a leap from our previous examples. The only differences are that we’re incrementing the counter on a pseudo-selector (input:checked) and displaying the counter just once in a dedicated .total element:

Controlling Increments

Counters don’t have to increment by 1. They can increment by any whole number you like. They can even decrement by using negative numbers. Building on our previous example, this one sets specific increment values for each selection:

See the Pen CSS Counter Game by Will Boyd (@lonekorean) on CodePen.

The syntax is simple enough:

While we’re on the subject, you can also control the starting value of a counter:

Potential Gotcha

An element with display: none on it will not increment a counter. If you want to hide an element but still have it contribute to a counter, you’ll have to hide it another way. Here’s one option:

Maybe you noticed, this is exactly what I’m doing in the last two examples. I hide the actual checkboxes for the sake of presentation, but still need them to increment the counter when checked.

Closing Remarks

Browser support for CSS counters is fantastic. Green across the board.

As awesome as CSS counters are, don’t forget about our old friends <ol> and <li>. They’re still great for a basic list of enumerated items. It’s the trickier situations that benefit from CSS counters, especially since they work on any element, giving you more freedom syntactically and semantically.

Update: I should mention accessibility. CSS counters rely on generated content in pseudo-elements. Some screen readers will pick up this content, some won’t. Because of this, it’s best not to rely on pseudo-elements for critical content. These demos were crafted to teach CSS counters in interesting ways, but I wouldn’t apply them in production as-is.

37 comments » Related topics:

Align 4 Retrospective: Writing a Multi-Threaded Game in JavaScript

I recently published my Align 4 game on CodePen. Simply put, it’s a Connect Four clone that you play against the computer. Before I say more, go play it.

Align 4 v3 screenshot


This is the third incarnation of this game that I have written. I made the first one a full decade ago (!) as a Windows app written in C++. Then I made the second one a few years ago using JavaScript and HTML5 canvas. And here’s the third one, still JavaScript, but more awesome.

The AI

The AI uses the minimax algorithm to pick its moves. It’s pretty standard stuff in game theory and works really well for this sort of game, where the win condition and the moves you are allowed to make are well defined.

In a nutshell, the computer is simply navigating a huge tree of potential moves. It looks at all the moves it could make, then every set of moves you could make in response, then every set of moves it could… You get the idea. The difficulty controls how many turns ahead it will look. It’s n+1, so on diffuculty 5 it’s looking 6 moves out. After evaluating all these potential moves, it picks the most promising branch and makes the corresponding move.

Sometimes, after the AI makes a move, it’ll show a message about “feeling saucy”. This happens when it evaluates a branch and realizes that it can win no matter what you do. Likewise, when you see a message about being “unsure”, that means the AI knows you can definitely win — assuming you play the right moves.

Web Worker

This was my first true multi-threaded JavaScript app. By default, JavaScript apps are single-threaded. All code runs in the main UI thread. One unfortunate effect of this is that if you have computationally expensive code running (in this case, evaluating a huge tree of potential moves), nothing else happens until the code is done. And I’m not just talking about the JavaScript. DOM elements can’t be added, CSS animations freeze, user clicks are not responded to. Basically, your browser tab locks up. No good!

And that’s where web workers come in. A web worker lets you run some JavaScript in a separate thread. So I put all the heavy AI stuff in a web worker, leaving my main thread free and responsive.

One caveat of using a web worker is that it must be in a separate file. The file is also subject to CORS security policies. You can try to work around the separate file requirement with creative use of Blobs, but IE10+ will treat them as cross-origin, so you’re kind of screwed there. Ultimately, my solution for publishing this on CodePen was to create 2 pens, one for the UI and another for the web worker.


I used a fair bit of animation to make interacting with the chips feel more natural. I calculate positioning offsets in code, and I scale the duration of the drop animation with however many rows the chip has to drop, but besides that all animations are done in CSS. This provides much better performance than jQuery tweening. There are no real “physics” to speak of, just liberal use of ease-in/ease-out to roughly emulate acceleration due to gravity.

Oh, and one huge tip: put backface-visibility: hidden on animated elements. This will solve some flickering problems and will fix the initial stutter with delayed animations.


I have playtested hundreds (thousands?) of games against the AI. Here are a few tips to help you win:

  • Don’t bother with obvious wins. The AI won’t not notice. It’ll block you every time.
  • The trick is to trap the AI. Set up situations where you create two winning moves at once, so the AI can only block one. Or set up a situation where if the AI did block you, it would create another winning move for you on top of the chip it just dropped.
  • Controlling the center of the board is more valuable than dropping chips along the edges.
  • Play defensively. Of course you want to block the AI getting four chips in a row, but blocking three chips in a row makes the board safer.

Finished Board Game AI

I originally planned for this to be a massive 9 part tutorial on building a game with an AI. Despite my “I will finish this even if it kills me” attitude, I finally changed my mind, deciding to cut to the chase and just post the end result. This came from the realization that even if I did finish writing the last third of the tutorial, it would still be boring and dense and honestly not something I was excited about. I was mistaking stubbornness for perseverance.

Don’t get me wrong, I’m not getting all emo here. I still have a working game AI that I’m proud of, and I’m still going to share it. So without further ado, click here to play my finished board game AI.

game AI screenshot

The presentation is all HTML5 Canvas. The AI uses a minimax algorithm to pick its moves. Feel free to view the JavaScript for all the source code. It’s pretty well commented, but if you have any questions, feel free to ask. After all, this was originally meant to be something people could learn from.

I call it “finished”, but code never is. The UI could be snazzier and the AI could use a few improvements. Maybe I’ll come back to it one day, but for right now, I’m ready for something else.

Update: Guess what? I came back to it. Read my Align 4 retrospective to find out about the updated version.

0 comments » Related topics:


The blog’s been a little sparse lately, but I’ve actually been working on something big. A few weeks ago, I made an HTML5 Canvas version of that Align 4 game I made 8 years ago. It’s a stripped down version, but my goal was to write a tutorial on how I made it.

The coding part was fine. The tutorial part was a terrible idea. So far I’ve written 6 out of 9 (!) posts that make up the full tutorial. I’m a terribly slow writer and the whole thing is sucking my soul out. Oh, and it’s kind of boring. Poring over hundreds of lines of JavaScript is not edge-of-your-seat excitement.

But I’m not giving up now, dammit! I’ll start publishing it on Monday, 3 posts a week, for 3 weeks. And then I’ll be done with it and move on to something else.

Also, I want to stop writing all these ultra-dense blog posts. Lighter stuff would be more fun.

In other news, I ordered Adobe CS5 Web Premium. I’m excited. The only thing I have now is Photoshop, but my copy is 3 versions behind. I’ve heard good things about Fireworks from other web designers, and I’d like to learn vector graphics with Illustrator. CS5 is basically a big toy box to me. I even ordered a second monitor to have more room to play.

Adobe CS5 Web Premium box