Stuff & Nonsense product and website design

Blog and all that malarkey

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 tagged this with css


More from Stuff & Nonsense

Andy Clarke demonstrates how to take your product and website designs to the next level.

Take your Squarespace designs to the next level with our premium Squarespace templates.

The popular web design contract template trusted by thousands of web designers and developers.

Andy is an experienced mentor who can help you improve your design skills and develop your career.


Hire me. I’m available now to work on product and website design projects.