CSS3's :not Selector
Published 24 September 10 by Justin French
I’ve been experimenting with some of the new CSS3 selectors, admiring IE9’s new selector support (finally!) and tinkering with JavaScript libraries like Selectivizr and IE7.js to see if they can help drag older versions of IE into the future kicking and screaming.
The :not selector helped me solve a pretty common problem today. We had a pretty standard unordered list, and we wanted to place a one pixel border in between each list item. Let’s start with the unsurprising mark-up:
<ul>
<li>Cats</li>
<li>Dogs</li>
<li>Mice</li>
</ul>
A common solution is to place a border-bottom on each <li>:
ul li {
border-bottom: 1px solid #ccc;
}
This doesn’t just place a border between each list item of course, it also adds a border after the last item. Something like this:

Sometimes this isn’t what I want, but I have options. I can use the :last-child pseudo selector to remove the border on the last list item:
ul li {
border-bottom: 1px solid #ccc;
}
ul li:last-child {
border-bottom:none;
}
IE6 doesn’t support this selector, so IE6 users will end up seeing that last border. Graceful degradation for the win, right? Sometimes this is acceptable, sometimes it isn’t. In some designs, I can work around this by repeating the same border on the top, left and right borders of the <ul>, creating a border the whole way around the list.
ul {
border:1px solid #ccc;
border-bottom:none;
}
ul li {
border-bottom: 1px solid #ccc;
}
Which results in something like this:

If that’s not going to work in the design, the next crutch I usually reach for is adding a last class to the last item in the list that I can hook into to remove the bottom border in all browsers:
ul li {
border-bottom: 1px solid #ccc;
}
ul li.last {
border-bottom: none;
}
This bugs me for a few reasons:
- We’ve added markup to the page purely for presentational reasons.
- I like writing code that’s easy for my co-workers to read and understand. In this case, we had to describe the one presentational goal (“put a border in between the list items”) with two rules. This means the reader needs to consider both rules before they can understand what the styles are trying to do. This may seem trivial, but this is a riciculously simple example — real world examples would probably have margins, padding, typography and hover styles adding noise.
- It’s code that’s looking backwards instead of forwards. We’re cluttering everything up to support an older browser which still a big deal for some websites.
This is where :not can help us! Here’s what I came up with:
ul li:not(:last-child) {
border-bottom: 1px solid #ccc;
}
Bam! That’s one simple rule that describes the visual treatment perfectly. No extra markup, no extra styles. Here’s what it looks like in IE9 and all other modern browsers:

Perfect! The trade-off, of course, is that older browsers like IE 6, 7 and 8 don’t understand :not (or :last-child for that matter). How did I decide that this was an acceptable trade-off?
- We have a eight of developers, all contributing in the view layer, but only a few of us spend time really testing the interface in all the browsers, and even fewer would spot that extra border on the bottom list item and realize that something wasn’t quite right.
- In the design we were working on, the list looked horrible in IE6 with that extra line — It wasn’t a gracefull degradation, it was an ugly line in the wrong place screwing up my design. We have a heap of IE6 users still, so this matters.
- The list actually looked pretty good in IE 6/7/8 without the borders.
- Less markup and less CSS means there’s less chance of screwing things up, and it just feels wrong to write extra markup or CSS to deal with a browser that was released nearly ten years ago.
So, for this particular design, with this team, with this set of users, with this web app, it turns out that progressive enhancement with a modern browser feature worked better than graceful degradation for an older browser. It might work for you too.
You can have your cake and eat it too! The really great news is that if you add-in something like Selectivizr with an IE-ony conditional comment, most of your IE6/7/8 users can also come to the party with an 8kb Javascript download.
I feel like I’m just scratching the surface with the :not selector. If you have some ideas how it can be used to enhance a design, or know of similar tutorials I should be reading and linking to, let me know on Twitter.
Before you go…
Here’s some links to my most popular posts: