Chapter 7: Modern CSS Selectors: :focus, :has, :is, :where

Smarter ways to target elements, write accessible interactions, and clean up your CSS


There was a time when CSS selectors were… limited.
You had to nest deep, chain ugly combos, or duplicate logic across multiple selectors.

But modern CSS gives us power tools: selector functions like :is(), :where(), :has(), and interaction states like :focus-visible.

They make your CSS:

Let’s break down what each one does, when to use them, and where they can help you simplify your styles.


Section 7.1: :focus vs :focus-visible

:focus

:focus-visible

button:focus-visible {
  outline: 2px solid blue;
}

button:focus {
  outline: none; /* suppress default */
}

Great for accessibility. Lets you show outlines only when they’re needed.

If you remove :focus outlines without replacing them with :focus-visible, you’re breaking keyboard accessibility.


Section 7.2: :is(): Write Less, Target More

:is() lets you group multiple selectors into one:

:is(h1, h2, h3, h4) {
  margin-top: 0;
}

Why it matters:

Before:

h1, h2, h3, h4 {
  margin-top: 0;
}

Fine, but brittle in longer selectors.

Now:

:is(article, section) > :is(h2, h3) {
  color: navy;
}

Clean and scoped.

Specificity: :is() takes the highest specificity of its arguments.


Section 7.3: :where() Like :is(), But Zero Specificity

Same usage as :is(), but the selector never contributes specificity.

:where(h1, h2, h3) {
  font-weight: 600;
}

Use it in resets and base layers to avoid specificity wars later.

:where(button) {
  border: none;
  font-family: inherit;
}

Section 7.4: :has(): Parent-Based Selector (Finally!)

This one is a game changer.

:has() lets you style a parent based on its children.

.card:has(img) {
  padding: 0;
}

Only apply padding if an image is present.

form:has(:invalid) {
  border: 2px solid red;
}

Highlight invalid forms without JS

Browser support: Chrome, Edge, Safari (yes) Firefox (no but coming soon)

Use it for:


Section 7.5: Real Use Cases

Button that only shows outline for keyboard users

button {
  outline: none;
}

button:focus-visible {
  outline: 2px dashed #333;
}

Contextual layout changes with :has()

.article:has(blockquote) {
  padding-left: 2rem;
  border-left: 4px solid #ccc;
}

Style form wrapper if any input is invalid

.form-group:has(input:invalid) {
  background: #ffe0e0;
}

Collapse heading margin across types

:where(h1, h2, h3, h4) + * {
  margin-top: 0;
}

Section 7.6: Performance and Specificity Tips


Summary

These selectors are not just syntax sugar, they enable patterns that were once JavaScript-only.


Resources


Coming Up Next

In Chapter 8, we’ll build accessible, semantic forms with labels, ARIA, validation, and keyboard-friendly patterns: no frameworks required.