FrontendPrep
Menu
Topics
Questions
Guides
Challenges
Soon
Back to CSS & Layouts Questions
cssMedium

CSS: Modern :has() Selector and Pseudo-classes vs. Pseudo-elements

Master pseudo-classes, pseudo-elements, and the modern native parent selector ':has()' to create complex relational styling rules without JavaScript.

CSS: Modern :has() Selector and Pseudo-classes vs. Pseudo-elements

A standard advanced layout CSS styling question is:

What is the difference between a pseudo-class and a pseudo-element? How does the modern ':has()' relational selector work, and how does it solve the historical 'parent selector' limitation in CSS?

Until recently, CSS selectors could only target elements downwards or sideways in the DOM tree. The introduction of the native :has() selector (supported in all major browsers) has revolutionized layout code, allowing developers to target elements based on their descendants or sibling elements.


1. Pseudo-classes vs. Pseudo-elements

Understanding the syntax difference (single colon : vs double colons ::) and functional differences is essential:

  • Pseudo-classes (:): Target a specific state of an element. They represent logical conditions (e.g., :hover, :focus, :first-child, :nth-child(2)).
  • Pseudo-elements (::): Create virtual elements or style specific sub-parts of an element. They represent content nodes that do not exist in the HTML markup (e.g., ::before, ::after, ::first-letter, ::placeholder).
/* Style a button when it is hovered (State check) */
button:hover {
  background-color: darkblue;
}
 
/* Insert virtual content before a heading (DOM addition) */
h1::before {
  content: "📌 ";
}

2. The Native Parent Selector: :has()

Historically, CSS lacked a parent selector. If you wanted to style a card container based on whether it contained an image or an active input field, you had to write custom JavaScript to toggle helper classes.

The :has() selector solves this. It is a relational pseudo-class that accepts a relative selector list as an argument. It selects the target element if any of the passed selectors match a descendant or sibling:

/* Target the .card parent, but ONLY if it contains an <img> tag */
.card:has(img) {
  padding: 0;
  border-radius: 8px;
}

3. Advanced Relational Selectors with :has()

:has() is not limited to parent-child queries. It can match adjacent elements and complex states:

A. Styling Form Groups on Focus

You can style a form field wrapper if any child input within it is active or focused:

/* Style the outer form-group container when its nested input is focused */
.form-group:has(input:focus) {
  border-color: #0066cc;
  box-shadow: 0 0 5px rgba(0, 102, 204, 0.5);
}

B. Previous Sibling Selection

Historically, the adjacent sibling selector (+) only let you style elements after a target. You couldn't style the sibling before the target. :has() resolves this:

/* Target an <h1> that is immediately followed by a <p> */
h1:has(+ p) {
  margin-bottom: 4px;
}

Senior-Level Interview Answer

Pseudo-classes target dynamic element states or positional relationships, whereas pseudo-elements insert virtual DOM nodes or style sub-parts of existing elements. The modern :has() selector is a relational pseudo-class that resolves CSS's historical parent selector limitation. It matches container elements based on arguments containing child, sibling, or state query parameters. The browser evaluates :has() relationships dynamically, updating parent styles in response to user actions on descendants (like hover or focus states). This lets developers write complex, reactive designs (such as grid adjustments based on item counts or error highlights on wrapper cards) entirely in native CSS, bypassing layout side effects and state manipulation in JavaScript.


Common Interview Mistakes

❌ Using :has() recursively

You cannot nest :has() selectors inside other :has() statements (e.g. :has(:has(div))). The specification explicitly forbids recursive nesting of relational selectors to avoid performance issues in browser rendering engine cascades.

❌ Mismatching colon count for pseudo-elements

Writing :before instead of ::before. While modern browsers support the single-colon fallback for legacy reasons, using double colons :: for pseudo-elements and a single colon : for pseudo-classes is the correct CSS3 standard syntax.


Key Takeaways

  • State vs Element: Pseudo-classes (:hover) evaluate structural or interaction states; pseudo-elements (::after) create virtual nodes.
  • Parent Selector: :has() is a relational pseudo-class that lets you style a parent element based on its descendant nodes.
  • Input Responsiveness: Useful for styling container fields or error borders based on nested focus or validation states.
  • Previous Sibling: Combines with adjacent selector tokens (+, ~) to style sibling elements that occur before a trigger.
  • No JS Required: Replaces JavaScript DOM-scanning callbacks with browser-native, performant layout styling declarations.

Share this Resource

Help other developers level up by sharing this study guide.

More Technical Questions

Expand your mastery. Deep dive into other frontend interview challenges in this category.