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.