Here is your mission, should you choose to accept it: create a table of items. Each item should span a third of the content area, with the fourth item starting on a new row, much like floats. However, a particular item must always display the price at the end of the first row.
So if there are only two elements, the price element would be second. But if there are more than three items, the price would be the last element in the first row.
Pure CSS Counting
I’ve gone all in on flexbox lately, teaching it right alongside floats as a layout method at our little project called HackerYou, and I have found that students take to it well. The flexbox specification contains properties that enable us to modify markup in new ways. One of these is
order, which allows us to modify the presentational order of content without touching the markup or structure.
Used with media queries,
order is extremely useful, enabling us to change the content’s order with the viewport size. That got me thinking: Why can’t we change the order of elements according to the amount of content?
What if we combined quantity queries and the
order property to change how content is read according to how much of it there is?
Flexbox, or the “Flexible Box Layout Module4,” is a CSS specification that allows for content to be laid out in any direction and for children to be sized to their parent easily. Originally introduced in 2009, flexbox has gone through many changes over the years. However, it is supported5 in all current browsers, with the exception of Internet Explorer 9+.
One of the most significant changes within flexbox is the naming syntax of associated properties and values. Because the specification evolved over years, browser vendors would use the syntax that was being developed at the time. So, using vendor prefixes is recommended to ensure support across legacy browsers.
Before we dig into quantity queries and how they work, we should understand how to use the
order property. First, we need to wrap the content with a parent element and apply
display: flex to it.
Here’s the HTML:
<div class="container"> <p class="itemOne">Hello</p> <p class="itemTwo">World!</p> </div>
And here’s the CSS:
.container display: flex;
By default, elements will appear in their order in the markup. All child elements of a flexbox parent share an
order value of
This value is unitless and simply refers to the order of the element relative to the other elements around it. However, we can change the value of an individual element using the
p.itemOne order: 2;
In the example above, we’ve changed the order of
p.itemOne to a value of
2, making it fall after
p.itemTwo and thereby changing the presentational order for the user. Note that the markup remains the same, however.
Counting With CSS
Media queries, eh? Such an awesome tool for applying CSS when certain conditions are met. Those conditions could be device type, size, color and more — pretty powerful stuff. But the query applies only to the device that the viewer is using; there is no defined CSS method for querying the amount of content in an element.
If we get creative with existing CSS pseudo-selectors, however, we can build tools that count the number of children in an element and then apply styles accordingly. For this example, let’s use a simple ordered list:
<ul class="ellist"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li class="target">6</li> </ul>
The magic of counting sibling elements is in the selector below. This example applies styles to elements when four or more are available.
ul.ellist li:nth-last-child(n+4) ~ li, ul.ellist li:nth-last-child(n+4):first-child // styles go here
Wait, No. That’s Insane!
Yep, that’s the selector. In English, it could be translated as this: “When there are four or more child elements, get the other list items and the first child.”
Let’s break this down. First, the counting:
ul.ellist li:nth-last-child(n+4) // Styles!
This translates as, “Go to the last child and count back four children.” Apply the styles to the fourth element and all elements before it.
Go ahead and experiment by editing the Codepen and changing the selector to a different number.
So, there it is, counting. If fewer than four siblings are counted, nothing is selected and no styles are applied. We can now modify this selector to select all
li elements using the general sibling combinator19.
ul.ellist li:nth-last-child(n+4) ~ li // Styles!
The problem is that this doesn’t select the first child element. We can append another selector to do that:
ul.ellist li:nth-last-child(n+4) ~ li, ul.ellist li:nth-last-child(n+4):first-child // Styles!
Of course, we can make the selector more agnostic simply by supplying the parent element and letting it choose all of the children. We do this with the
element > *:nth-last-child(n+4) ~ *, element *:nth-last-child(n+4):first-child // Styles!
Ordering Based On Quantity
Now that we have explored how to count with CSS selectors and how to use flexbox to order content, let’s mix them together to build a tool that orders elements based on the number of siblings.
Again, we’re trying to make our last element be the third element (i.e. appear as the last element in the first row) when there are more than three siblings.
Let’s apply some CSS for some presentational styling. We’ll apply
display: flex to the parent container, which allows us to apply the
order property on the child elements. As well, we’ll apply some default styling to the
.target element to differentiate it.
ul.ellist margin: 20px 0; padding: 0; list-style: none; display: flex; flex-flow: row wrap; ul.ellist > * border: 10px solid #27ae60; text-align: center; flex: 1 0 calc(33.33% - 20px); padding: 20px; margin: 10px; .target color: white; background: #2980b9; border: 10px solid #3498db; ul.ellist, ul.ellist > * box-sizing: border-box; ul.ellist margin: 20px 0; padding: 0; list-style: none; display: flex; flex-flow: row wrap; ul.ellist > * border: 10px solid #27ae60; text-align: center; flex: 1 0 calc(33.33% - 20px); padding: 20px; margin: 10px; ul.ellist .target color: white; background: #2980b9; border: 10px solid #3498db;
Now that we have a base style to work with, we can create some logic to order our items accordingly. Again, by default, all elements have an
order value of
1 and are displayed according to the order in which they appear in the markup.
Using a quantity query, we can count whether there are more than three items.
ul.ellist > *:nth-last-child(n+3) // Styles!
We can then modify our query to select the
.target element only if the number of items is met. For now, we’ll apply an
-1, so that it appears at the beginning of our list.
ul.ellist > *:nth-last-child(n+3) ~ .target order: -1;
Voilà! We’ve just styled an element based on the numbers of siblings it has. Using this code, we can put one element in front of another.
But what if it needs to go between items?
Some Logical Thinking
Here is the problem, in three arguments:
- By default, all items have an
1. We need the items at the beginning of the list to keep that
- Our target will be presented at the end of the first row. So, we need the target’s
ordervalue to be higher than the ones in the beginning — i.e.
- We need all items from three onward to have a higher
orderthan our target and lead elements. So, they will have an
How about this?
Because all items have a default value of
1, we don’t need to declare that. Let’s allow our target element to have an
order value of
2 via our quantity query, effectively placing it higher than the others.
ul.ellist > *:nth-last-child(n+3) ~ .target order: 2;
Then, using another quantity query that utilizes
nth-child(), we will count from the beginning of the list, rather than from the end. Because our
.target quantity query is more specific, the last element will be ignored, but all others three and higher will have their order changed.
ul.ellist > *:nth-last-child(n+3) ~ .target order: 2; ul.ellist > *:nth-child(n+3) order: 3;
Whoa! Let’s Go Over That Again
We counted from the end of a parent element if there were a number of child elements. If there were, we applied some styles to an element of our choice. We then counted from the beginning and applied styles to all elements past that point.
The beautiful part is that if we were to remove elements, the target element would still appear in the correct position.
<ul class="ellist"> <li>1</li> <li>2</li> <li>3</li> <li class="target">4</li> </ul>
The Resulting Task
My first thought when given this task was to use a programming language. Because the website was built on WordPress, I could modify “the loop” to count and inject the element where needed.
However, because I’m building the website for a front-end development school, I wanted to do it with pure CSS and explore the possibilities that flexbox’s
Below is an example of the resulting page, done entirely with CSS.
Using It In The Real World
Quantity queries are a fairly new concept, and writing the selectors can be a bit of a challenge. Nevertheless, the community is embracing the concept and is building tools and writing Sass mixins that can help us write queries effectively. Libraries such as the one by Daniel Guillan23, called a Quantity Queries Mixin24, enable us to write media queries as simple includes.
@include at-least($count) …
@include between($first, $last) …
The plugin allows for custom pseudo-selectors to target values within a certain range, at least, or at most.
p:at-least(4) … p:between(4,6) …
Currently, support for both CSS pseudo-selectors and flexbox28 is great in modern browsers. If your project targets users on IE 10 and above, you can use quantity queries and flexbox ordering together.
Where to Use It
When building websites, we often use programming languages that allow us to count and modify our content as needed. However, as CSS has improved, we’ve moved away from programming languages into advanced CSS properties.
Quantity ordering enables us to remove modified loops that count and insert content accordingly, allowing our content to be read semantically.
A great example of the usefulness of quantity ordering would be a news website with advertising. In the markup, all articles are organized together, and the ads are placed at the end. In terms of accessibility, this allows for an uninterrupted flow of content. The ads can then be placed throughout the content, using quantity ordering only on a presentational layer.
While ordering can be used to change the presentational display of elements, for accessibility, it can damage the experience. Be aware of how content flows and how it will be read on accessibility devices.
Quantity queries and quantity ordering are advanced concepts and might be scary for beginners. However, as we move presentational styling away from programming languages and into CSS, we should use these tools more and more.
(ds, ml, al)
- 1 http://lea.verou.me/2011/01/styling-children-based-on-their-number-with-css3/
- 2 http://andr3.net/blog/post/142
- 3 http://alistapart.com/article/quantity-queries-for-css
- 4 http://www.w3.org/TR/2015/WD-css-flexbox-1-20150514/
- 5 http://caniuse.com/#search=flex
- 6 https://github.com/postcss/autoprefixer
- 7 ‘http://codepen.io/drewminns/pen/LVVXxz/’
- 8 ‘http://codepen.io/drewminns’
- 9 ‘http://codepen.io’
- 10 ‘http://codepen.io/drewminns/pen/oXXQBo/’
- 11 ‘http://codepen.io/drewminns’
- 12 ‘http://codepen.io’
- 13 ‘http://codepen.io/drewminns/pen/WvvYyN/’
- 14 ‘http://codepen.io/drewminns’
- 15 ‘http://codepen.io’
- 16 ‘http://codepen.io/drewminns/pen/Pqqvqp/’
- 17 ‘http://codepen.io/drewminns’
- 18 ‘http://codepen.io’
- 19 http://www.w3.org/TR/selectors/#general-sibling-combinators
- 20 ‘http://codepen.io/drewminns/pen/waarLQ/’
- 21 ‘http://codepen.io/drewminns’
- 22 ‘http://codepen.io’
- 23 http://www.danielguillan.com/
- 24 https://github.com/danielguillan/quantity-queries
- 25 http://jamessteinbach.com/
- 26 http://www.sitepoint.com/using-sass-quantity-queries/
- 27 https://github.com/pascalduez/postcss-quantity-queries
- 28 caniuse.com/#search=flexbox
- 29 ‘http://codepen.io/drewminns/pen/vOLGPg/’
- 30 ‘http://codepen.io/drewminns’
- 31 ‘http://codepen.io’
- 32 https://github.com/marcj/css-element-queries
- 33 https://www.flickr.com/photos/markusspiske/18544440564/in/album-72157644611527928/