DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

CSS Counters: auto-update list numbers without JavaScript

This article was originally published on Rails Designer Blog


This is a quick-tip about a CSS feature. It is the kind of feature most developer (and LLM's) would use JavaScript for. With CSS you get this for free. Knowing about it, makes you a better developer!

Recently I helped build a new product that involved pages with content in a specific order.

For the ordering I used the nice positioning gem. This gives sequentially numbering (1, 2, 3 and so on).

So you can display them like this:

The ERB is something simple like this:

<%# locals: (page:) %>
<li draggable data-reposition-id-value="<%= page.id %>">
  <%= tag.span page.position class: "" %>

  <%= tag.p page.content %>
</li>
Enter fullscreen mode Exit fullscreen mode

(reposition is coming from the Kanban article)

But, of course, the pages needed to be sortable. So I used the Stimulus controller from the Create a Kanban board with Rails and Hotwire and, yay, in no-time, pages could be sorted in a custom way:

But wait, the numbers are now incorrect. I can refresh the page and the numbers will match up again, but this is of course not acceptable.

So how'd you do this? If I hadn't already hinted at CSS above, would you reach for JavaScript? Extend the reposition Stimulus controller? Or add a dedicated controller for this purpose? It would likely be only a 10-line controller, so why not?

Because CSS has a feature that can help you with this: CSS counters.

CSS counters automatically track and display numbers based on the DOM order. Not the database order. Add this to the wrapping ul element [counter-reset:page-num] and update the page partial like this:

<%# locals: (page:) %>
<li draggable data-reposition-id-value="<%= page.id %>">
  <%= tag.span class: "before:content-[counter(page-num)] [counter-increment:page-num]" %>

  <%= tag.p page.content %>
</li>
Enter fullscreen mode Exit fullscreen mode

If you use realvanilla CSS, you can use:

ul { counter-reset: page-num; }

li span::before {
  content: counter(page-num);
  counter-increment: page-num;
}
Enter fullscreen mode Exit fullscreen mode

So how do CSS counters work? You initialize a counter with counter-reset on a parent element. Then each time you use counter-increment, it bumps up by one. Finally, counter() displays the current value. The browser handles all the counting automatically as elements move around in the DOM.

Why not use ol>li and style the ::marker pseudo-selector? Great question! The ::marker pseudo-element is quite limited in what you can style. You can change colors, fonts and the content itself. But that's about it. No backgrounds, borders, padding or positioning. If you need more control over the visual styling of your numbers, like adding backgrounds, custom spacing or complex layouts (like I needed in above product, but not showed), you'll need to use CSS counters with regular elements instead.

And that is it. Yet another powerful CSS feature you can use instead of JavaScript! ❤️

Top comments (0)