SASS mixin using :nth-child to create dynamic rounded-corner grid

This is a technique utilizing CSS3 pseudo class selectors for dynamic content, which often time you won’t know how many are there. And using classes like item-1, item-2, etc. just seems impractical and not flexible enough in code.

To further illustrate what I’m talking about. Here are the results I would like to achieve:

Basic Rules

  1. The first box in the first row has a top left round corner.
  2. The last box in the first row, if it is in the last column, has a top right round corner.
  3. The first box in the last row has a bottom left round corner.
  4. The last box in the last row, if it is in the last column, has a bottom right round corner.

So if we have 9 items in 3 columns …

9 by 9 Grid

And if we have 8 items in 3 columns …

2a2e62538c91dda85a6475ae18705bf8

Then if we have 8 items in 4 columns …

5816b681987ab98c6828e681f492a525

Breaking down the code

HTML


<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
    <li>9</li>
</ul>

First, I’ll start the SASS mixin by setting two parameters, one for the number of columns ($n-columns), another for the radius ($corner-radius). The list items also need to float and have equal widths that can fit in nicely to form the columns.


@mixin round-corner-grid($n-columns, $corner-radius: 1em) {
  $unit-width: 100%/$n-columns;

  li {
    float: left;
    width: $unit-width;
  }
}

Then I will use :first-child and :nth-child pseudo class selectors to select the first and nth elements, given that n is the number of columns we want and the variable is $n-columns here. If you want to know how :nth-child selectors work, Chris Coyier has an excellent blog post about that. Notice that I am also using the border radius mixins from Compass.


  /* Target the first box in the first row */
  li:first-child {
    @include border-top-left-radius($corner-radius);
  }
  
  /* Target the last box in the first row in the last column */
  li:nth-child(#{$n-columns}) {
    @include border-top-right-radius($corner-radius);
  }

The tricky part is to target the boxes in the last row. If there are 9 items in 3 columns, then we want the 7th item to have a bottom left round corner and the 9th item to have a bottom right corner. If there are 8 items in 3 columns, then we only need to worry about the 7th item, because the last box is the 8th one and in the 2nd column, not the last. However, if there are 8 items in 4 columns, then the 5th and 8th items are the ones we want to target respectively. Therefore, we need to get a bit mathematical here:

:nth-child(3n + 1) selects 1st, 4th, 7th, … elements, so that means :nth-child(xn + 1) selects 1st, (x + 1)th, (2x + 1)th, … elements. To target the elements in the last row, we can do :nth-child(xn + x), but we only want the last item of all, so it will be :nth-child(xn + x):last-child.

:nth-last-child(i) selects the i-th element to the last, so if we have 9 items in 3 columns, we want the third to the last item using :nth-last-child(2), which is selecting the 7th item. But since the total number of items is unpredictable and we only want the one that sits in the first column. So we should look through all the items in the last row (except for the last item) and if it is one of the :nth-child(xn + x) elements, then it is our target.


  /* Target the first box in the last row */
  li:nth-child(#{$n-columns}n+1) {
    @for $i from 1 through $n-columns {
      &:nth-last-child(#{$i}) {
        @include border-bottom-left-radius($corner-radius);
      }
    }
  } 
  
  /* Target the last box in the last row in the last column */
  li:nth-child(#{$n-columns}n+#{$n-columns}):last-child {
    @include border-bottom-right-radius($corner-radius);
  }

Then we will call the mixin for the ul.


 ul {
   @include round-corner-grid(3);
 }

Demo

You may try changing number of columns and add or remove some list items and see the results changed.

Check out this Pen!