CSS Supply and Demand


* Codepen Embeds are weird, click the Rerun button to reload them.

Here’s the mission, if you choose to accept it:

Create a table of items. Each item, a third of the content area, breaking to the next row much like floats. However, there is always a specific item that displays the price at the end of the first row.

If there’s only one element, the price item is the second one, but if there are more than 3 items, it’s the last element in the first row.

You may first think, hmmm, JavaScript. Easy, loop over the items and if there are more than 3, update the styling. However, what if I told you could do it with just CSS?

Pure CSS Counting

I’ve gone all-in on Flexbox lately. I’ve gone so far as teaching Flexbox right along side Floats at HackerYou as anyone should in 2015, and students have taken to it well.

Within the Flexbox spec, holds so many tools that allow us to present our content based on presentation. One of these properties is order, a property that allows us to modify the presentational order of our content, while our markup and structure remains untouched.

The order property used with media queries is extremely useful, allowing us to change the order of our content based on device size. So that got me thinking, why can’t we change the order of content, based on the amount of content?

Quantity Queries

Quantity Queries, an idea explored by Lea Verou, André Luis and Heydon Pickering is a concept in which the number of sibling elements is counted, and styles are applied if the supplied amount of siblings are present.

What if we combined quantity queries and the order property together to change how our content is read depending on how much content there is?

Understanding Order

Before we dig in to quantity queries and how they work, it’s best to understand how the order property can be utilized.

To use the order property, we need to create a parent element that wraps the content we want to order, and apply a CSS rule of display: flex to it.

// HTML
<div class="container">
  <p class="itemOne">Hello</p>
  <p class="itemTwo">World!</p>
</div>
// CSS
.container {
    display: flex;
}

See the Pen Really Good Work Article by Drew Minns (@drewminns) on CodePen.

By default, elements are ordered by the order in which they are marked up, and thus all child elements of a flex parent share an order value of 1. The value provided is unitless and simply refers to the order of the item to the others around it. However, we can change that value on individual element with the order property.

p.itemOne {
    order: 2;
}

See the Pen Really Good Work Article by Drew Minns (@drewminns) on CodePen.

In the example above, we changed the order of p.itemOne to a value of 2. A value higher than the default value of 1. So p.itemOne now falls after p.itemTwo, moving the presentational view to the end. Note how the markup remains the same however.

Counting CSS

Media Queries eh? Really awesome tools that allow us to apply CSS when a condition is met. Those conditions? Device type, size, colour, etc. Pretty powerful stuff, however the query only applies to the device that the client is using to view our content. There is no defined CSS tool method to query the amount of content that exists.

Being creative with existing CSS pseudo-selectors however, we can build tools that will count the amount of children within an element, and apply styling accordingly.

The magic to counting sibling elements is in the magic selector below. This example below is to apply styles when there are 4 or more available.

ul li:nth-last-child(n+4) ~ li,
ul li:nth-last-child(n+4):first-child {
    // styles go here
}

See the Pen WvvYyN by Drew Minns (@drewminns) on CodePen.

Wait, no. That’s insane

Yeah, that’s the selector, when there are four or more child elements, get the other li’s, and the first child.

Let’s break it down nice and easy.

First, the counting

ul li:nth-last-child(n+4) {
    // Styles!
}

This means, “go to the last-child and count backwards”. Four children in our case. Apply styles to the fourth element and all elements before it.

Go ahead, edit the codepen and change the selector to a different number.

See the Pen Pqqvqp by Drew Minns (@drewminns) on CodePen.

So there ya go, counting. If there are less than 4 to count, it selects nothing. Now, we can modify this selector to select all li elements using the General Sibling Combinator.

ul li:nth-last-child(n+4) ~ li {
    // Styles!
}

Problem is that we don’t select the first-child due, so we can change our selector to include the first-child as well.

ul li:nth-last-child(n+4) ~ li, ul li:nth-last-child(n+4):first-child {
    // Styles!
}

Of course, we can make the selector more agnostic, by simply supplying the parent element, and letting it choose the children.

element > *:nth-last-child(n+4) ~ *, element *:nth-last-child(n+4):first-child {
    // Styles!
}

Ordering based on quantity

So we explored how to count with CSS selectors and how to use flexbox to order content. Now let’s discuss how to mix them together to build a tool that orders based on amount of the elements.

Again, the task is to have our last element, be the third element when there are more than 3 elements.

To start this off, let’s build out our markup.

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li class="target">6</li>
</ul>

Let’s apply some CSS to style for some presentational styling. We’ll apply display: flex to the parent container in order to use the order property on the child elements. As well, we’ll apply some default styling to the .target element to differentiate it.

ul {
    margin: 20px 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-flow: row wrap;
    > * {
      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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Now that we have a base style we can create some logic to order our items accordingly. By default, all elements have an order of 1, and are displayed based on the order in which they appear in the markup.

Using a quantity query we can count if there are more than 3 items.

ul > *:nth-last-child(n+3) {
    // Styles!
}

We can then modify our query to select our .target element only if the amount of items is met. For now, we’ll apply an order of -1, so that it appears at the beginning our list.

ul > *:nth-last-child(n+3) ~ .target {
    order: -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Voila!

We were able to able styles to an element based on if there was more than three sibling elements. Problem is that putting something in front of another is great and all, but what if it needs to go between items.

Some logical thinking

So, by default, all items have an order set to 1. I need the items at the beginning of my list, to keep that order value.

My target is to be at the end of the first row, so I need it’s order value to be higher than the ones at the beginning, so 2.

Here in lies the problem, I need all items from 3 onward to have a higher order than my target, and lead elements. So an order value of 3.

How about this?

Since 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 our others.

ul > *:nth-last-child(n+3) ~ .target {
    order: 2;
}

Then using another quantity query, we’ll count from the beginning of the list, rather than the end. Because our .target quantity query is more specific, the last element will be ignored, but all others 3 and higher will have their order changed.

ul > *:nth-child(n+3) {
    order: 3;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Mic Drop

Whoa, let’s go over that again

So, we counted from the end of a parent element, if there were a number of child elements, and if there were, applied some styles to an element. We then counted from the beginning and applied styles to all elements passed that point.

The beautiful part is that if we were to remove elements, the target element would still appear in the right position.

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li class="target">4</li>
</ul>
  • 1
  • 2
  • 3
  • 4

The resulting task

Again, my first thought when given this task was to use a programming language. Since the site was built on WordPres, I could modify the loop to count and inject the dive where needed.

However, being that I’m building the site for a front-end dev school, I wanted to do it with pure CSS.

Here is an idea of the resulting page, done entirely with CSS.

See the Pen waarLQ by Drew Minns (@drewminns) on CodePen.

If someone wants to turn it into a mixin, I’d greatly appreciate it.

Related Posts

Making Sense of Clip Path

Stars, squares and animations using data points.

An inline-block intervention

In an effort to explain why whitespace dependent elements are bad for your structural layout.