The :not(), :is(), :where(), and :has() CSS Selectors
Modern Functional Pseudo-Classes
CSS provides four powerful functional pseudo-classes that let you write more concise, flexible selectors. These selectors accept other selectors as arguments, enabling complex targeting with minimal code.
:not() — The Negation Selector
:not() targets elements that do NOT match the given selector:
/* All links that don't have a class */
a:not([class]) {
text-decoration: underline;
color: #3498db;
}
/* All inputs except submit buttons */
input:not([type="submit"]) {
border: 1px solid #ddd;
padding: 0.5rem;
}
/* All children except the last */
li:not(:last-child) {
border-bottom: 1px solid #eee;
}Multiple Negations
/* Elements that are not a heading and not a paragraph */
:not(h1):not(h2):not(h3):not(p) {
font-size: 0.9rem;
}
/* Modern: multiple selectors in one :not() */
:not(h1, h2, h3, p) {
font-size: 0.9rem;
}Practical :not() Patterns
/* Style all form inputs except disabled ones */
input:not(:disabled) {
cursor: pointer;
}
/* All buttons except primary */
.btn:not(.btn-primary) {
background: white;
color: #333;
}
/* Remove margin from last item without targeting it directly */
.card:not(:last-child) {
margin-bottom: 1.5rem;
}
/* All images that don't have alt text */
img:not([alt]) {
border: 3px solid red; /* Accessibility debugging */
}:is() — The Matches-Any Selector
:is() matches any element that matches ANY of the selectors in its argument list. It reduces repetition in complex selectors:
/* WITHOUT :is() - repetitive */
header a:hover,
nav a:hover,
footer a:hover {
color: #3498db;
}
/* WITH :is() - concise */
:is(header, nav, footer) a:hover {
color: #3498db;
}More :is() Examples
/* Style headings inside articles or sections */
:is(article, section, aside) :is(h1, h2, h3) {
color: #2c3e50;
line-height: 1.3;
}
/* Interactive states for multiple elements */
:is(.btn, .link, .tab):is(:hover, :focus) {
opacity: 0.8;
}
/* All headings */
:is(h1, h2, h3, h4, h5, h6) {
font-family: 'Poppins', sans-serif;
line-height: 1.2;
}:is() Specificity
Important: :is() takes the specificity of its most specific argument:
/* Specificity = (0,1,0,0) because #id is the most specific */
:is(.class, #id, element) {
color: red;
}:where() — Zero-Specificity :is()
:where() works exactly like :is() but has zero specificity. This makes it easy to override:
/* Specificity: (0,0,0,0) — very easy to override */
:where(header, nav, footer) a {
color: #3498db;
}
/* This overrides the above easily */
a.special {
color: red;
}:where() for Default Styles
:where() is perfect for creating base styles that are easy to override:
/* Base styles with zero specificity */
:where(.card) {
padding: 1.5rem;
border-radius: 8px;
background: white;
}
:where(.btn) {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* Component-specific overrides win easily */
.card {
padding: 2rem; /* Overrides :where(.card) */
}:is() vs :where() Comparison
/* :is() has the specificity of its most specific argument */
:is(.card) p { color: blue; } /* Specificity: (0,0,1,1) */
/* :where() always has zero specificity */
:where(.card) p { color: blue; } /* Specificity: (0,0,0,1) */:has() — The Parent Selector
:has() is the most revolutionary CSS selector. It lets you select a parent based on its children — something that was impossible in CSS before:
/* Select a card that contains an image */
.card:has(img) {
grid-column: span 2;
}
/* Select a form that has an invalid input */
form:has(input:invalid) {
border: 2px solid #e74c3c;
}
/* Select a label whose adjacent input is focused */
label:has(+ input:focus) {
color: #3498db;
}Practical :has() Examples
/* Style parent when child is hovered */
.nav-item:has(a:hover) {
background: #f5f5f5;
}
/* Card layout changes when it has an image */
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
.card:not(:has(img)) {
display: block;
}
/* Section with no content */
section:has(> :only-child) {
padding: 2rem;
}
/* Style based on checkbox state */
.settings:has(input[type="checkbox"]:checked) {
background: #e8f5e9;
}:has() as a Conditional Selector
/* If the page has a sidebar, adjust main content width */
.page:has(.sidebar) .main-content {
max-width: 70%;
}
.page:not(:has(.sidebar)) .main-content {
max-width: 100%;
}
/* If a list has more than 5 items, use smaller font */
ul:has(li:nth-child(6)) {
font-size: 0.9rem;
}Combining Functional Pseudo-Classes
/* All links in header/footer that are not active */
:is(header, footer) a:not(.active) {
opacity: 0.7;
}
/* Cards that have images but not videos */
.card:has(img):not(:has(video)) {
aspect-ratio: 16/9;
}
/* Base styles easy to override */
:where(:is(h1, h2, h3)) {
margin-bottom: 1rem;
}Summary
Modern CSS functional pseudo-classes make selectors more powerful and concise. Use :not() for exclusion, :is() for grouping with normal specificity, :where() for grouping with zero specificity (ideal for defaults), and :has() for parent selection based on children. These selectors reduce code repetition, eliminate the need for extra CSS classes, and enable patterns that were previously impossible without JavaScript.