Hiding empty elements with CSS :empty and :has()

You might be used to adding and removing .open and .closed classes on divs and containers to control their state styles. What if we could just write CSS that reflected the state of the DOM, without having to additionally handle state by toggling classes? In this post, we’ll explore how to hide elements with the :empty pseudo-class if they have no children, and how to make the pattern more granular and practical when we combine it with :has() and :not().

Hiding an empty element

The :empty matches any element that has no children. The pseudo-class is supported by all major browsers, and is safe to use even if you’re targeting Internet Explorer. We can use it in combination with the display property to hide an element if it’s empty:

.container:empty {
  display: none;
}

In this example, the :empty pseudo-class is used to select elements with the class .container that have no children (including text nodes), and the display: none rule is used to hide them.

<!-- This will be visible -->
<div class="container">Some text</div>

<!-- This will be hidden -->
<div class="container"></div>
Some text

Hiding an element that has an empty child

Assume that we have a some HTML markup that looks something like this, that we dynamically populate with suggestions inside of .results

<div class="container">
  <h4>Suggestions</h4>
  <div class="results">
    ...
  </div>
</div>

…and we want to hide the entire .container when the .results div is empty (since the container itself will never be empty). For scenarios like this, we can combine the the :empty pseudo-class with :has(), to hide any .container that has an empty .results div:

.container:has(.results:empty) {
  display: none;
}
<!-- This will be visible -->
<div class="container">
  <h4>Suggestions</h4>
  <div class="results">
    <div>Result 1</div>
    <div>Result 2</div>
    <div>...</div>
  </div>
</div>

<!-- This will be hidden -->
<div class="container">
  <h4>Suggestions</h4>
  <div class="results"></div>
</div>
Suggestions
Result 1
Result 2
...

Here, .container selects all .containers, and then :has() filters them to only those that have an empty .results div. Note that .has() is only supported by 84.68% of all major browsers, and you may want to use a polyfill while awaiting broader support.

Hiding a parent element that doesn’t contain a certain child

You can equally choose to hide a container based on if it doesn’t contain a certain child, say a .result. Imagine that our markup looks something like this, where we return a series of .result divs:

<div class="container">
  <h4>Suggestions</h4>
  <div class="result">...</div>
  <div class="result">...</div>
  <div class="result">...</div>
</div>

For a scenario like this, we can combine the :not() pseudo-class with :has() to hide the .container if it doesn’t contain any .result elements:

.container:not(.container:has(.result)) {
  display: none;
}
<!-- This will be visible -->
<div class="container">
  <h4>Suggestions</h4>
  <div class="result">Result 1</div>
  <div class="result">Result 2</div>
  <div class="result">...</div>
</div>

<!-- This will be hidden -->
<div class="container">
  <h4>Suggestions</h4>
</div>
Suggestions
Result 1
Result 2
...

Here, we start by selecting all .container elements, then we exclude elements from that list with :not(), and we exclude all .container elements that contain a .result. What remains is any .container that doens’t include a .result, and we use display: none to hide it. Note that unlike :has(), :not() is actually supported by all major browsers, and can safely be used without a polyfill.

We might not be able to avoid toggling classes completely to handle states, but with the help of these patterns we can to a larger extent let our styles be a function of the content that’s being displayed, and build more robust experiences.