CSS3 structural pseudo-classes
The more often you code websites, the more often it's likely you'll need to solve the same problem again and again. Let's consider a typical example. Horizontal navigation bars are often made up of a number of equally spaced
- links. Suppose we need margin to the left and right side of each list item, except for the first and last list item. Historically, we have been able to solve this problem by adding a semantically superfluous classname to the first and last
- elements in the list, as shown in the highlighted lines in the following code snippet:
And then by adding a couple of rules in the CSS, we can amend the margin for those two list items:
li {
margin-left: 5%;
margin-right: 5%;
}
.first {
margin-left: 0px;
}
.last {
margin-right: 0px;
}
This works but isn't flexible. For example, when building a website built on a CMS system, list items for linking new content might be added automatically, so it might not be a simple task to add or remove the last or first class to the correct list item in the markup.
The :last-child selector
CSS2.1 already had a selector applicable for the first item in a list:
However, CSS3 adds a selector that can also match the last:
Using these selectors together, we don't need any additional classes in our markup.
We'll fix up our And the winner isn't... site navigation using this and a combination of the display: table property. The following screenshot shows how things look currently:
Now, let's take a look at the graphic mockup:
The navigation bar links span the full width of the design, which we need to replicate. Our markup for the navigation looks like this:
First, we'll set the nav element to be a table:
nav {
display: table;
/* more code... */
}
Then the
to be displayed as a table-row:
nav ul {
display: table-row;
/* more code... */
}
And finally the list-items to display as table-cells:
nav ul li {
display: table-cell;
/* more code... */
}
This means that if extra list items are added, they will automatically space themselves accordingly. Finally, we'll use our CSS selectors to align the text to the right and left of the first and last list items:
nav ul li:last-child {
text-align: right;
}
nav ul li:first-child {
text-align: left;
}
Then in the browser, our navigation is approaching our original composite:
Don't worry; these tables are only for display!
You may be wondering what on earth I'm thinking of, to suggest that we use a table for the navigational layout. However, don't forget, these tables are only presentational. That means they exist only in the CSS and are nothing to do with the markup. We are merely telling the browser we want those elements to appear and behave as if they were a table, not actually be a table. Displaying the markup in this manner also doesn't preclude us from using a different layout type for a different viewport, for example, display: inline-block for viewports below 768 px.
The nth-child selectors
But what about those alternate colors shown in the navigation bar links of the original composite? Again, CSS3 has a selector that can solve this problem for us without the need for additional markup:
Let's use this selector to fix the problem and then we can look at some of the many ways that nth-child can solve problems that previously required extra markup. I'll add alternate red links in the navigation bar by adding the following style rule:
nav ul li:nth-child(even) a {
color: #fe0208;
}
And now we have alternate colors in the navigation links:
How about that? Not a line of jQuery in site and no extra markup! What did I tell you? CSS3 selectors are great!
Understanding what nth rules do
Amongst frontend web developers and designers, nothing makes mathematics weaklings tremble quite like the nth-based rules (well, you know, except maybe someone asking you to code a little PHP or give them a hand with some REGEX expressions). Let's see if we can make sense of the beast and gain a little respect from those backend wizards.
When it comes to selecting elements in the tree structure of the DOM (Document Object Model or more simplistically, the elements in a page's markup) CSS3 gives us incredible flexibility with a few nth-based rules—:nth-child(n), :nth-last-child(n), :nth-of-type(n), and :nth-last-of-type(n). We've seen that we can use (odd) or (even) values (as we have to fix our navigation above) but the (n) parameter can be used in another couple of ways:
-
Used as an integer; for example, :nth-child(2)—would select the second item
-
Used as a numeric expression; for example, :nth-child(3n+1)—would start at 1 and then select every third element
The integer based property is easy enough to understand, just enter the element number you want to select. The numeric expression version of the selector is the part that can be a little baffling for mere mortals. Let's break it down. For practicality, within the brackets, I start from the right. So, for example, if I want to figure out what (2n+3) will select, I start at the right (from the third item) and know it will select every second element from that point on. I've amended our navigation rule to illustrate this:
nav ul li:nth-child(2n+3) a {
color: #fe0208;
}
As you can see, the third list item is colored and then every subsequent second one after that (if there were 100 list items, it would continue selecting every second list item):
How about selecting everything from the second item onwards? Well, although you could write :nth-child(1n+2), you don't actually need the first number 1 as unless otherwise stated, n is equal to 1. We can therefore just write :nth-child(n+2). Likewise, if we wanted to select every third element, rather than write :nth-child(3n+3), we can just write :nth-child(3n) as every third item would begin at the third item anyway, without needing to explicitly state it.
The expression can also use negative numbers for example, :nth-child(3n-2) starts at minus 2 and then selects every third item. Here's our navigation amended with the following rule:
nav ul li:nth-child(3n-2) a {
color: #fe0208;
}
And here's what it gives us in the browser:
Hopefully, that's making perfect sense now?
The child and last-child differ in that the last-child variant works from the opposite end of the document tree. For example, :nth-last-child(-n+3) starts at 3 from the end and then selects all the items after it. Here's what that rule gives us in the browser:
Finally, let's consider :nth-last-of-type. Whilst the previous examples count any children regardless of type, :nth-last-of-type let's you be specific about the type of item you want to select. Consider the following markup:
Note that the second list item doesn't have the internal class added to it.
Consider the following rule:
nav ul li.internal:nth-of-type(n+2) a {
color: #fe0208;
}
You can see that we are telling the CSS, "From the second matching item, target every
- item with a class called internal. And here's what we see in the browser:
CSS3 doesn't count like jQuery!
If you're used to using jQuery you'll know that it counts from 0 upwards. For example, if selecting an element in jQuery, an integer value of 1 would actually be the second element. CSS3 however, starts at 1 so that a value of 1 is the first item it matches.
The negation (:not) selector
Another handy selector is the negation pseudo-class selector. This is used to select everything that isn't something else. For example, keeping the same markup as the previous example, if we change our rule as follows:
nav ul li:not(.internal) a {
color: #fe0208;
}
You can see that we are opting to select every list item that doesn't have the internal class . So in the browser, we see this:
So far we have looked primarily at what's known as
structural pseudo-classes (full information on this is available at
http://www.w3.org/TR/selectors/#structural-pseudos). However, CSS3 has many more selectors. If you're working on a web application, it's worth looking at the full list of UI element states pseudo-classes (
http://www.w3.org/TR/selectors/#UIstates), as they can; for example, help you target rules based on whether something is selected or not.