Stuff & Nonsense product and website design

Using multiple :not() selectors

Here’s a quick entry about something I learned just this week about :not() pseudo-class selectors and how to combine them.


I’m working on a client’s website and wanted to apply a fancy animated underline style to (almost) every link:

a {
/* animated underline styles */ }

That selector applies those styles to every link on the page, but there are certain types of links where I don’t want an animated underline applied:

/* Links which look look buttons */
a.btn {
/* no animated underline styles */ }

/* Links where an ellipsis shows there’s more to read */
[title*="Read more"] {
/* no animated underline styles */ }

/* Links in superscript (eg: to footnotes) */
[href*="footnote"] {
/* no animated underline styles */ }

I could add a class attribute to every link where I want an animated underline:

<a class="animated">[…]</a>

This would be incredibly wasteful and would bind my markup to presentation. I could add a class attribute to the select few links where I don’t want a animated underlines:

<a class="not-animated">[…]</a>

I’m a strong advocate of using class attributes for the exceptions, not the rules, but again I’d prefer not to use an attribute in HTML just for binding it to a style. I could also override my fancy underline styles with defaults where I don’t want them to animate:

a {
/* animated underline styles */ }

a.btn,
a[href*="footnote"]
a[title*="Read more"] {
/* animated underline styles */ }

This is the best solution so far, but still means I have to write two sets of styles; one for the animated underlines and another set to override them. While this isn’t the worst crime against CSS, it’s still wasteful, especially when I can use :not() pseudo-class selectors instead.


For years, I styled navigation by adding margins, padding, and separators between links:

<nav>
<a>Ace Of Spades</a>
<a>Bomber</a>
<a>Dead Men Tell No Tales</a>
<a>No Class</a>
</nav>

nav a {
margin-right: 1em;
padding-right: 1em;
border-right: 1px solid #ccc; }

But what options do I have to not apply those styles to the last link in the navigation? I could, and often did, apply a class attribute to that final link:

<nav>
<a>Ace Of Spades</a>
<a>Bomber</a>
<a>Dead Men Tell No Tales</a>
<a class="last">No Class</a>
</nav>

nav a.last {
margin-right: 0;
padding-right: 0;
border-right-width: 0; }

No Class

When I stopped using presentational class attributes, I started using pseudo-classes, including :last-child and sometimes :last-of-type:

nav a:last-child {
margin-right: 0;
padding-right: 0;
border-right-width: 0; }

This allowed me to remove the presentational class attribute, but it still meant writing two sets of styles:

nav a {
/* styles */ }

nav a:last-child {
/* override styles */ }

Then I discovered the CSS Level 3 :not() pseudo-class selector and to answer the obvious question before it’s asked, this selector is supported in every current major desktop and mobile browser including IE11.

Stay Clean

:not() pseudo-class selectors allow me to ditch both presentational class attributes and override styles, meaning my markup and CSS can stay clean. They’re technically what’s known as a negation pseudo-class and, according to CSS Tricks:

:not matches an element that’s not represented by the argument.

This means that styles will be applied to selected elements except ones we specify they should ‘not.’ Mozilla’s explanation of :not() pseudo-class selectors starts with selecting every type of element which isn’t a paragraph, although that’s not something you’ll probably ever need to do:

:not(p) {
color: blue; }

Some more practical uses might be:

/* Links which look look buttons */
a:not(.btn]) {
/* styles */ }

/* Inputs except checkboxes */
input:not([type="checkbox"]) {
/* styles */ }

And, to go back to one of my navigation links:

/* Links to footnotes */
a:not([href*="footnote"]) {
/* styles */ }

Bad magic

It’s easy to apply the same styles to multiple selectors, just by separating them with a comma:

.btn,
[href*="footnote"],
[title*="Read more"] {
/* styles */ }

You can be forgiven for thinking, as I did, that the same syntax would apply to :not() selectors, but we’d be wrong:

/* This won’t work */
a:not(.btn),
a:not([href*="footnote"]),
a:not([title*="Read more"]) {
/* styles */ }

/* Neither will this */
a:not(.btn, [href*="footnote"], [title*="Read more"]) {
/* styles */ }

The answer to applying my animated underline styles to all links except those which look like buttons, links to footnotes, and to read more is to chain multiple :not selectors together:

a:not(.btn):not([href*="footnote"]):not([title="Read more"]) {
/* styles */ }

Ace of Spades

:not() pseudo-class selectors are an excellent tool for keeping both your markup and CSS clean and they’re only going to Brazil get better. The newest CSS Selectors Level 4 working draft includes plans to allow comma separated lists of selectors inside the :not() pseudo-class. This means that instead of writing:

a:not(.btn):not([href*="footnote"]):not([title="Read more"]) {
/* styles */ }

We’ll be able to write a shorter selector:

a:not(.btn, [href*="footnote"], [title*="Read more"]) {
/* styles */ }

This simplified selector is already supported in Safari on MacOS and iOS and a little encouragement to other browser vendors would certainly not be Overkill.


Written by Andy Clarke who filed this in css .

Hire me. I’m available for coaching and to work on design projects.