Messing with quantity queries in CSS

I recently read posts by Aaron Gustafson and Heydon Pickering about quantity queries, and I thought they would make a really good continuation to my previous post on flexbox grids. The aim of the last post was to figure out how to make each item in a grid equal height regardless of how much content each item contains. This means that content is less constrained by the amount of space available and instead, the space is created around the content.

This time I'm looking at how we can fill the entire space of a grid using any number of items. For example, this is how my original grid looked. Notice the poor lonely item at the end.

In this example, I am going to alter the width and height of items depending on their position on the grid. To do this, I will use quantity queries in CSS.

Counting items

Normally, you or I would determine the quantity of items in a given container by counting them. However, CSS doesn’t provide a method for this, so we have to solve the problem using a pretty clever combination of selectors.

The closest I can get to counting the items in my grid is using the :nth-last-child(n) selector. This will count from the end of the group to the beginning. So, by giving 'n' a value of 5, I can target the item that is fifth from the end. Of course, the grid needs to contain at least five items to work.

Using the grid above, let's say I want make the first item full width so we don't end up with an empty space at the end. Now this is where it gets interesting because I can use concatenation to add more conditions. I can combine :first-child with :nth-last-child(5) to say that the targeted item must be both fifth from the end and the first child. The CSS would look like this:

li {
    width: 50%;
}
li:nth-last-child(5):first-child {
    width: 100%;
    color: red;
}

This is how it looks in a browser. You can see that the styles have targeted the first item in the grid, which is also fifth from the end. However, if I add another item to the grid, the styles will no longer apply because both conditions won't be met.

5 items in a grid

Tackling the unknown

This is all great, but it doesn't yet solve the problem of having an unknown quantity of items in the grid.

Let's see what I do know about this grid. I know that the two column grid works well with an even number of items, but when there is an odd number of items, I want to make the first item full width. This is where the odd rule comes in handy. Just like before, I can use :nth-last-child(n) to count back from the end, but this time I will replace 'n' with 'odd'. This will select every odd item in the set, counting back from the end. Then I can add the first-child condition like before, so that the first item will be selected if it's position on the grid is an odd number. The code looks like this:

li:nth-last-child(odd):first-child {
    width: 100%;
    color: red;
}

Here is the grid with both an odd number of items and an even number of items.







Awesome! Now my grid changes depending on how many items it contains. My work is almost done.

This grid works well on small-medium screen sizes, but on larger screens like a desktop, I think it will look better in three columns. So, I'm going to put the styles I've just created into a set of media queries so that they only take action on small-medium screens. This creates a whole new problem because targeting odd and even items won't produce the desired result on a three column grid.

Well, what do I know about the layout of a three column grid? I know that the space is filled perfectly when the total number of items is a multiple of three. I have also figured out that when the grid contains a total of 2,5,8,11,14, etc items, it falls one item short of a perfectly filled grid, like below.


For this case, I'm going to fill the entire grid space by giving the first two items a width of 50% each.

Let's look at how I can do this. 2,5,8,11, etc is a sequence of numbers which can be represented by the nth term 3n-1. So, going back to my old friend the nth-last-child selector, I will use :nth-last-child(3n-1) to target those values. Now I can target items which are positioned at 3n-1 from the end of the grid, and like before I can add the first child condition. This time I've used the adjacent sibling selector to target the second li in the set.

li:nth-last-child(3n-1):first-child,
li:nth-last-child(3n-1):first-child + li
    {
        width: 50%;
        color: pink;
    }

Let's see how this looks.

Great! Now I have a perfectly filled grid space for any number that is a multiple of 3 or 3n-1.

I have figured out that this leaves me with one more sequence of numbers to tackle. When the grid contains 1,4,7,10 etc items, it falls two items short of a full grid. This sequence of numbers can be represented by the nth term 3n-2, so I can do exactly the same as I just did above. In order to fill the grid space, I have decided to make two items on the grid 66.6% width.

li:nth-last-child(3n-2):first-child,
li:nth-last-child(3n-2):nth-child(4) {
    width: 66.6%;
    color: gold;
}

In particular case, I've decided to give the first and fourth children a width of 66.6%. Here is how it looks.


Now I'm happy. My two and three column grids will accommodate any number of items. I've used media queries to change between one, two and three column layouts depending on screen sizes.

This whole example is on CodePen. Have a look and play around with it if you like.

Browser support

Support for the :nth-last-child() selector appears to be good for all browsers, from IE9 and above and recent mobile browsers.

Another example

Heydon Pickering's example is based on a navigation, which is a really good use case for quantity queries. When lots of items are added to the navigation, the layout changes to allow more items to fit into one row. Heydon's example is also on CodePen.

Tags: