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 .


Would you like advice and inspiration on making better designs for the web?

Get monthly design inspiration and insights based on my 25+ years of experience. View some recent emails, sign up today, and get:

    I promise never to share your email address and you can unsubscribe with just one click.

    Free set of Layout Love grid templates when you sign up today.

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