Using Sass for “Quantity Queries”

By James Steinbach

Technically, CSS doesn’t have the ability to count an element’s siblings. However, Heydon recently showed us what he calls quantity queries: a clever combination of :nth-last-child(), :first-child, and ~ to style elements based on how many elements are in a single parent. Have a look at that A List Apart article and my interactive demo on CodePen to get a feel for what’s going on.

How Quantity Queries Work

Here’s the basic gist of quantity queries: by using :nth-last-child($n) and :first-child together, you can identify an element that is a certain number from the end of its siblings and the first child of its parent. The number from the end tells us the number of siblings. For example, the selector :nth-last-child(6):first-child will only select the first element in a group of 6 elements. If you select that element and all of its following siblings using ~, you can write custom CSS for the group of siblings based on how many there are.

:nth-last-child(6):first-child ~ * {
    // unique CSS for elements when there are 6 of these

This code lets you target a specific count, but let’s say you want to change styles when there are “at least” or “less than” a certain number. You can do that by modifying the :nth-last-child() value. To target groups of at least 6 items, use :nth-last-child(n + 6):first-child and for groups of less than 6, use :nth-last-child(-n + 6):first-child.

Writing Quantity Queries with Sass

The code above is great, but let’s use Sass to make it a litte DRY-er: we don’t need to rewrite the entire syntax everywhere we want to use this technique. We’ll start off with a single mixin that takes two parameters: the number and the comparison:

@mixin quantity-query( $number, $comparison: 'equal' ) {
    // generate code in here

Here $number will be an integer: what you want the CSS to “count”. The $comparison will be one of three values: greater, less, or equal. We’ll specifiy equal as the default $comparison value if none is specified.

The first thing we’ll do inside our mixin is set the :nth-last-child() value based on $number and $comparison:

@if index( ('greater' 'more' '>'), $comparison ) {
    $nth: 'n + #{$number}';
  } @else if index( ('less' 'fewer' '<'), $comparison ) {
    $nth: '-n + #{$number}';
  } @else if index( ('equal' 'same' '='), $comparison ) {
    $nth: $number;
  } @else {
    @warn "Sorry, that’s an invalid $comparison value."

We’ve got a little freedom here: you can specify greater, more, or '>' for the greater than $comparison. You can specify less, fewer, or '< ' for the less than $comparison. You can specify equal, same, or '=' for the equal to $comparison. Note: if you use the symbols, be sure to wrap them in quotes.

Based on the $comparison value, the :nth-last-child() value is set as a string. We’re using Sass interpolation inside that string (#{$variable}) because we want the number to be part of a string, not part of an equation.

After we’ve got the right value for :nth-last-child(), we’ll output our selectors:

&:nth-last-child(#{$nth}):first-child {
    & ~ * {

The & at the front means that we’re attaching the pseudo-selectors to the selector in which you use this mixin. If you put it inside a .item {} block, you’ll get .item:nth-last-child…. Inside the selector for the first element, there’s another & followed by & ~ *: this applies the styles to the first element of the group and all its siblings. Note: I know, not everyone likes * selectors, but to protect Sass nesting, that’s the safest option for us here.

The @content; directive simply repeats whatever CSS you write inside the mixin when you use it.

Bonus Mixins for Speed!

We can use this mixin just fine as it is:

.menu-item {
  @include quantity-query(5, greater) {
    color: blue;

… and we’ll get:

.menu-item:nth-last-child(n+5):first-child ~ * {
  color: blue;

But we can make this better with alias functions:

@mixin qq-equal( $number ) {
  @include quantity-query( $number, equal ) {

@mixin qq-greater( $number ) {
  @include quantity-query( $number, greater ) {

@mixin qq-less( $number ) {
  @include quantity-query( $number, less ) {

Now you can use a unique mixin for each comparison value if that makes your workflow better.

Beyond Menus

So far most of the demos of this feature are menus. But this technique can do so much more! Here’s an example of a real-live use case I was able to solve with quantity queries.

A designer recently gave me a comp for a page that displayed four categories of content: each category had a title and a list of links to individual pieces of content. The most reasonable markup for this content is:

<div class="content-category">
  <h3 class="content-category-title">Title</h3>
  <ul class="content-category-list">
    <li class="content-category-list-item">Item</li>

On desktops, these categories are laid out in columns. That’s simple enough. However, the designer saw a different layout for smaller screens: all 4 titles should be at the top of the page and act as toggles to show/hide the items in their lists below all the titles. On the left side of the following image, you can see the “desktop” layout; on the right side of the divider, you can see the “mobile” layout:

Quantity Queries

The shortcut solution here would probably have been to create a second container with copies of the titles and use those elements for toggle triggers on small screens, then hide it on larger screens. But on the other hand, we could keep the good markup I described above, and use quantity queries to fix our layout. Here’s how I did it:

  1. On small screens, position the titles absolutely; position them statically after the larger breakpoint.
  2. Use quantity queries to determine how far from the top the list should be positioned on small screens.

While the comps started with 4 categories, I’m always suspicious that in the future, the client may want 3 or 5 or 7 categories. To prepare for those cases, I used a @for loop twice in this code. First, I looped through 1 through $max ($max is currently set at 7, but can be tweaked to account for higher limits in the future). The loop inside the .content-category block (line 55) calls the qq-equal() mixin and creates the padding necessary at the top of each list based on how many categories there are. The loop inside the .content-category-title block (line 93) doesn’t use a quantity query; it merely sets the top position of each title based on which category it’s in (:nth-child()).

Here’s a demo from CodePen:

See the Pen Using Sass for “Quantity Queries” by SitePoint (@SitePoint) on CodePen.


With a couple @for loops and our quantity query mixins, we were able to maintain sensible semantic markup and create an advanced responsive layout for this design. Feel free to grab the mixins from this Sassmeister gist and share your creative quantity query uses in the comments!



Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.