CSS Programming Using CSS Pseudo Classes and Combinators

Published Aug 21, 2020
Updated Sep 15, 2021
By Simon

That got your attention and so it should, but what do I mean exactly by CSS Programming? Well in this article we are going to look at complex selector combinations using CSS Pseudo Classes and CSS Combinators. However, first lets look at why.

A practical guide to CSS pseudo classes and combinators.

The Problem

The problem is that the last row in a Flexbox display with justify-content: space-between doesn't work exactly how most would like or think, that is the space that is left is split evenly and placed in the space-between as the below example illustrates.

1
2
3
4
5
6
/* CSS for the above layout */
.main-css {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  min-width: 300px;
  max-width: 1200px;
  margin: 20px 0;
  background-color: lightgrey;
  height: auto;
}
.main-css > div {
  flex-basis: 23%;
  background-color: #5F3BB3;
  min-height: 20px;
  margin-bottom: 20px;
}

To fix this problem we need to set a right margin explicitly to "push" the item across to the left. So I thought Stackoverflow 1 to the rescue, but not quite, the top voted answer works but as stated in the comment the items are 25% wide which actually leaves no space-between if you make the items 24% with space-between the solution does not work. Some of the other solutions do work though and I had been using one with a 3 items per row Flexbox display but when you get 4 items per row you have a problem that sometimes the last item's right margin needs to take up 2 item spaces and sometimes only 1 item space.

I had known of this problem for quite some time and at the beginning of this year, while doing the 100 Days Of Code challenge I decided to look at CSS in-depth and this is what I worked out one night while looking at CSS Pseudo Classes and Combinators.

Flex box align last row to grid

The Solution

For the example I have done a row of 4 and explicitly set the margin-right of the final flex item.

So I was thinking there must be a way to select an exact item based on the last item and there is, you can use :last-child pseudo class, but if the item is 1 or 4 in the line then it doesn't need a right margin, it is only items 2 and 3 in the last row that need a margin applied, so we need to have a combination to select these cases somehow.

 

:nth-of-type()

To find items that are at a specific location is fairly easy using :nth-of-type() selector and with a formula, we can select every nth item. To select every item at a certain position in the row we can use :nth-of-type(Cn + O), where Cn is a counter and O is the offset. So to choose an item in a row with 4 items per row we can use 4n and then to pick each second item in the row we can use + 2. So the final rule will be :nth-of-type(4n+2 ) to select items 2, 6, 10, 14... for as long as the set is.

.main-css > div:nth-of-type(4n+2) {
  background-color: darkorange;
}
1
2
3
4
5
6
7
8
9
10

 

:last-child(n)

Well, that's cool. Though we only want the last item and if it is the 2 item in the row, for that we can use :last-child? not so quick because then we would need to have :nth-of-type(4n + 2) & :last-child and there isn't a combinator selector for that. We can however use :last-child(n) and select the 2nd to last like below. The (n) counts back from the end.

.main-css > div:nth-last-child(2) {
  background-color: greenyellow;
}
1
2
3
4
5
6

 

sibling selector (+) combinator

The adjacent sibling selector (+) combinator selects the sibling item next to the first selector. If we do 2nd to last + the 2nd in the row like this div:nth-last-child(2) + div:nth-of-type(4n+2) we can find this one-off item. For the second rule it doesn't need the wrapper div, just the item, this doesn't work .main-css > div:nth-last-child(2) + .main-css > div:nth-of-type(4n+2).

.main-css > div:nth-last-child(2) +  div:nth-of-type(4n+2) {
  background-color: deeppink;
}
1
2
3
4
5
6
7
8
9
10

Now just add a right margin, calculated and explicitly set as 51.5% or maybe more correctly spaces between of 8% / 3 * 2 + 2 * 23% = 51.33333%.

.main-css > div:nth-last-child(2) +  div:nth-of-type(4n+2) {
  background-color: orangered;
  margin-right: 51.5%;
}
1
2
3
4
5
6
7
8
9
10

To do the 3rd item in the last row we can use :nth-of-type(4n+3) like so .main-css > div:nth-last-child(2) + div:nth-of-type(4n+3) and set the margin-right to 25.66%. So that's it, this works with space between, space around, and gap properties. You can change the number of items per row based on @media queries and adjust nth-of-type(Cn+O) and the margin-right(s) and never have last rows space out again.

I think that this type or selection opens up a lot of opportunities not only for the last row in flexbox but for other creative uses too, can you think of any? @ me at twitter and I'll add your comment here until I get a comment system set up.

Tags