Selecting Beyond the Dropdown
The CSS :has() pseudo-class is often shorthand for “parent selector,” but that undersells its flexibility. It can apply styles up the DOM tree based on a wide range of conditions. The <select> element is a particularly good playground: its behavior is explicitly conditional, with distinct states for whatever option happens to be selected. Pair :has() with that, and you gain page-level styling control driven by a user’s choice — no JavaScript required.
<select>
<option value="1" selected>Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
<option value="4">Option 4</option>
<option value="5">Option 5</option>
</select>
The standard markup above is straightforward: a dropdown with several <option> elements, and you add the selected attribute to the first option to establish the default state.
This is nothing new for styling individual controls; CSS has long supported the :checked pseudo-class. The limitation is that you can only style the selected option itself, or at best, its children. You cannot reach upward from a checked option to affect its parent or ancestors. For instance, applying a color and background-color change that responds to the currently selected option is trivial for the <option> element.
See the Pen [demo 01 - Using the :has selector on a dropdown menu](https://codepen.io/smashingmag/pen/oNOwded) by Amit Sheen.
Styling the Select Itself
:has() flips the direction. You can now start from the selected option and target the <select> element that contains it. This works because :has() checks for a descendant matching a selector — in this case, an <option> with a specific [value] that is also :checked.
See the Pen [demo 02 - Using the :has selector on a dropdown menu](https://codepen.io/smashingmag/pen/eYoRopZ) by Amit Sheen.
This behavior opens the door to useful validation scenarios. Consider a <select> with the required attribute. No option being selected is a valid edge case, but with :has() you can target this state directly. The selector checks if the required element does not have a selected option by combining :not() with :has() and :checked.
See the Pen [demo 02.1 - Using the :has selector on a dropdown menu](https://codepen.io/smashingmag/pen/jORLoVM) by Amit Sheen.
Scoping Styles Up the Chain
Why stop at the <select>? Since :has() can be applied on any ancestor, you can trace up as far as the :root element. To check if *any* select on the page has a particular option selected, the selector can walk the entire tree from the document root down.
:root:has(select [value="foo"]:checked) {
// Styles applied if <option value="foo"> is <select>-ed
}
This pattern is particularly useful for defining custom properties globally or for applying page-spanning aesthetic changes. A style picker can change an element's border-color based on a selection some distance away.
See the Pen [demo 03 - Using the :has selector on a dropdown menu](https://codepen.io/smashingmag/pen/yLrXroO) by Amit Sheen.
The same technique is well-suited for theme switching, where a whole-page background and foreground color scheme depends on a choice.
See the Pen [demo 04 - Using the :has selector on a dropdown menu](https://codepen.io/smashingmag/pen/OJGgjaJ) by Amit Sheen.
One important engineering consideration is specificity. When you have numerous <select> elements on a page, a broad check could accidentally trip multiple times. The solution is to give each target a class and reference it specifically within the :has() filter. This prevents the selector from matching unwanted dropdowns.
You also don't have to send styles all the way to the document root to find value. If the styling is meant for a contained widget, the selector can be scoped to that element's parent. A star-rating component that conditions its look on the chosen vote is a perfect example of this.
See the Pen [demo 05 - Using the :has selector on a dropdown menu](https://codepen.io/smashingmag/pen/rNbwvqz) by Amit Sheen.
Lasting Value
Treating :has() as just a parent selector does it a disservice. It's better understood as a conditional operator that propagates state upwards, unlike prior tools like the checkbox hack, which could not traverse the DOM in this way. Applying this logic to a <select> destination allows for dynamic custom properties and component-level theming without a single line of scripting.



