You’ve heard of CSS Grid, I’m sure of that. It would be hard to miss it considering that the whole front-end developer universe has been raving about it for the past year.
Whether you’re new to Grid or have already spent some time with it, we should start this post with a short definition directly from the words of W3C:
Grid Layout is a new layout model for CSS that has powerful abilities to control the sizing and positioning of boxes and their contents. Unlike Flexible Box Layout, which is single-axis–oriented, Grid Layout is optimized for 2-dimensional layouts: those in which alignment of content is desired in both dimensions.
In my own words, CSS Grid is a mesh of invisible horizontal and vertical lines. We arrange elements in the spaces between those lines to create a desired layout. An easier, stable, and standardized way to structure contents in a web page.
Besides the graph paper foundation, CSS Grid also provides the advantage of a layout model that’s source order independent: irrespective of where a grid item is placed in the source code, it can be positioned anywhere in the grid across both the axes on screen. This is very important, not only for when you’d find it troublesome to update HTML while rearranging elements on page but also at times when you’d find certain source placements being restrictive to layouts.
Although we can always move an element to the desired coordinate on screen using other techniques like translate
, position
, or margin
, they’re both harder to code and to update for situations like building a responsive design, compared to true layout mechanisms like CSS Grid.
In this post, we’re going to demonstrate how we can use the source order independence of CSS Grid to solve a layout issue that’s the result of a source order constraint. Specifically, we’re going to look at checkboxes and CSS Counters.
Counting With Checkboxes
If you’ve never used CSS Counters, don’t worry, the concept is pretty simple! We set a counter to count a set of elements at the same DOM level. That counter is incremented in the CSS rules of those individual elements, essentially counting them.
Here’s the code to count checked and unchecked checkboxes:
<input type="checkbox">Checkbox #1<br>
<input type="checkbox">Checkbox #2
<!-- more checkboxes, if we want them -->
<div class="total">
<span class="totalChecked"> Total Checked: </span><br>
<span class="totalUnChecked"> Total Unchecked: </span>
</div>
::root {
counter-reset: checked-sum, unchecked-sum;
}
input[type="checkbox"] {
counter-increment: unchecked-sum;
}
input[type="checkbox"]:checked {
counter-increment: checked-sum;
}
.totalUnChecked::after {
content: counter(unchecked-sum);
}
.totalChecked::after {
content: counter(checked-sum);
}
In the above code, two counters are set at the root element using the counter-reset
property and are incremented at their respective rules, one for checked and the other for unchecked checkboxes, using counter-increment
. The values of the counters are then shown as contents of two empty <span>
s’ pseudo elements using counter()
.
Here’s a stripped-down version of what we get with this code:
See the Pen CSS Counter Grid by CSS-Tricks (@css-tricks) on CodePen.
This is pretty cool. We can use it in to-do lists, email inbox interfaces, survey forms, or anywhere where users toggle boxes and will appreciate being shown how many items are checked and how many are unselected. All this with just CSS! Useful, isn’t it?
But the effectiveness of counter()
wanes when we realize that an element displaying the total count can only appear after all the elements to be counted in the source code. This is because the browser first needs the chance to count all the elements, before showing the total. Hence, we can’t simply change the markup to place the counters above the checkboxes like this:
<!-- This will not work! -->
<div class="total">
<span class="totalChecked"> Total Checked: </span><br>
<span class="totalUnChecked"> Total Unchecked: </span>
</div>
<input type="checkbox">Checkbox #1<br>
<input type="checkbox">Checkbox #2
Then, how else can we get the counters above the checkboxes in our layout? This is where CSS Grid and its layout-rendering powers come into play.
Adding Grid
We’re basically wrapping the previous HTML in a new <div>
element that’ll serve as the grid container:
<div class="grid">
<input type=checkbox id="c-1">
<label for="c-1">checkbox #1</label>
<input type=checkbox id="c-2">
<label for="c-2">checkbox #2</label>
<input type=checkbox id="c-3">
<label for="c-3">checkbox #3</label>
<input type=checkbox id="c-4">
<label for="c-4">checkbox #4</label>
<input type=checkbox id="c-5">
<label for="c-5">checkbox #5</label>
<input type=checkbox id="c-6">
<label for="c-6">checkbox #6</label>
<div class=total>
<span class="totalChecked"> Total Checked: </span>
<span class="totalUnChecked"> Total Unchecked: </span>
</div>
</div>
And, here is the CSS for our grid:
.grid {
display: grid; /* creates the grid */
grid-template-columns: repeat(2, max-content); /* creates two columns on the grid that are sized based on the content they contain */
}
.total {
grid-row: 1; /* places the counters on the first row */
grid-column: 1 / 3; /* ensures the counters span the full grid width, forcing other content below */
}
This is what we get as a result (with some additional styling):
See the Pen CSS Counter Grid by Preethi (@rpsthecoder) on CodePen.
See that? The counters are now located above the checkboxes!
We defined two columns on the grid element in the CSS, each accommodating its own content to their maximum size.
When we grid-ify an element, its contents (text including) block-ify, meaning they acquire a grid-level box (similar to block-level box) and are automatically placed in the available grid cells.
In the demo above, the counters take up both the grid cells in the first row as specified, and following that, every checkbox resides in the first column and the text after each checkbox stays in the last column.
Since we didn’t change the source order, the counter works and we can see the running total count of checked and unchecked checkboxes at the top the same way we did when they were at the bottom. The functionality is left unaffected!
To be honest, there’s a staggering number of ways to code and implement a CSS Grid. You can use grid line numbers, named grid areas, among many other methods. The more you know about them, the easier it gets and the more useful they become. What we covered here is just the tip of the iceberg and you may find other approaches to create a grid that work equally well (or better).
I had no idea that css counters was a thing!
The same could habe been achieved with flexbox’s order property, no need to define a grid layout.
The same could be achieved IF your design can be realized with flexbox. But if you NEED grid…
I agree with hannenz on this one, flex box would have been more straightforward in this situation. Great article though
Of course you can also do this same thing without Grid, using Flexbox’s
order
property.Thank you! Interesting example.
Several people are pointing out that flexbox’s
order
can be be used to achieve this, butorder
is not accessibility–friendly, and should generally be avoided. I’ll have to test out Grid’s approach to this and see if it’s more screen reader friendly.Both flexbox and grid follow the same model with regard to content re-ordering. The tab order and non-visual presentation of the document will follow the document source order. See the spec
I’m so glad someone mentioned accessibility.
Users with screenreaders get the info, you provide. They understand that there is a menu to choose from, not that it is very basic calculator. – If this really is a list of items to chose from, they will appreciate the summary at the end oft the checkbox group, I guess. But they do NOT hear the changes immediately after clicking (use live regions for this).
Btw: you really should use labels!
Anyway: is it a useful information that 2 of 6 items are checked? – I think I would appreciate in such a summary to know ”’which”’ items a selected.
Also: I hope, nobody gets the idea of building a small pure css calculator, because: The separation of concerns is an important principle. Don’t use css for calculating. JS is made for this! Using CSS instead is just a hack…
There are cases where CSS calculating is useful to change the design of something to indicate a certain threshold is reached or something like this for people that can see colors.
In this example I understand the counter as a simple usability enhancement, that should work – most screenreaders read out the content of :after and :before. Still it smells like a hack – at least a seducing, beautiful, straight forwarded one ;-)
The whole point of ordering elements with css is to keep the structure accessible, right?
Not impressed by the usage of grid (we had plenty of time to be impressed by CSS Grids :D), but very creative use of counters! Nice!