CSS counters to the rescue
Sunday 6 October 2024 • Reading time: 4 minutesAs I work with a huge fleet of websites, I externalized every bit of code I could into a mutualized library. Obviously, the main goal is to avoid redundancy between projects. In order to preserve flexibility, some components are simply providing markup and logic. Doing so, each website can provide its own styles when sourcing code from this library.
My use case: a carousel with numbered slides
One of the first externalized components was the infamous carousel. Time after time I added more personalization options to the markup and logic: horizontal sliding transition, previous and next controls, current element indicators with dots, touch support, etc. The source file of this specific component was becoming a bit of a mess.
With the complete revamp of Valraiso's website, our graphic designer added a nice carousel on the home page.
The one from our common library was to be put to good use!
As you can see in this picture, almost everything was already taken care of; the only missing part was on the current slide counter.
Instead of implementing the feature right away, I left the classic, too much used // TODO
. As expected, this comment stayed in place way too long.
A CSS only approach
After joking for two years about this with our graphic designer, I finally asked an intern to take care of the problem, as it seems to be a great exercise.
While he was starting to add more and more logic to the javascript, I thought it would be interesting to go another way: after all, only this website needed this specific feature; why add weight to all websites code bases?
I think it is easy to say it was a good call: problem was solved with only 3 lines of CSS.
Here is what we had to do in order to make it work.
Our markup was looking like this. For a variable number of sections, our component is spawning a corresponding dot. The current dot is identified with a specific CSS class: carousel__dot--current
.
html<div class="carousel"> <div class="carousel__inner"> <section></section> <section data-current=""></section> <section></section> <section></section> <section></section> </div> <div class="carousel__dots"> <button class="carousel__dot"></button> <button class="carousel__dot carousel__dot--current"></button> <button class="carousel__dot"></button> <button class="carousel__dot"></button> <button class="carousel__dot"></button> </div> </div>
With that in mind, we only had to declare a counter and increment it for each .carousel__dot
:
css.carousel__dot { counter-increment: home-carousel-dots; }
Now, by using this value at two different places, it was done.
css.carousel__dot--current::before { content: "0" counter(home-carousel-dots); /* in our case 02 /* }
css.carousel__dots::after { content: "/0" counter(home-carousel-dots); /* /05 */ }
The secret resides in the fact that depending on the scope in which you use your counter, it doesn't have the same value.
At the .carousel__dot--current
level, the counter is equal to the current count of .carousel__dot
in the HTML. But above, at the .carousel__dots
level, a count has been made, so the counter is equal to the total of .carousel__dot
in the HTML.
The rest was simply positioning and styling.
I'm always amazed by the capabilities of basic CSS over javascript solutions. We tend to often overlook other, simpler viable options.