Hide a div tag if the next ul's li's are all displayed none

Viewed 42

I have some HTML with divs and next sibling uls like this:

<div id="someid" class="someclass">
    <div class="title"></div>
    <ul>
        <li style="display: none;">1</li>
        <li style="display: none;">2</li>
        <li style="display: none;">3</li>
        <li>4</li>
    </ul>
    <div class="title"></div>
    <ul>
        <li style="display: none;">1</li>
        <li style="display: none;">2</li>
        <li style="display: none;">3</li>
        <li style="display: none;">4</li>
    </ul>
    <div class="title"></div>
    <ul>
        <li style="display: none;">1</li>
        <li>2</li>
        <li style="display: none;">3</li>
        <li style="display: none;">4</li>
    </ul>
</div>

How to make the div with class title display: none if next ul's li's are all display: none?

2 Answers

Unfortunately, today's CSS can't do that (see below for tomorrow's CSS), but with JavaScript you can loop through the .title elements and see if their next element is a ul and all of its child elements are display: none, and set the title element to display: none if so:

for (const element of document.querySelectorAll(".title")) {
    if (element.nextElementSibling.tagName === "UL" &&
        [...element.nextElementSibling.children].every(
           (child) => getComputedStyle(child).display === "none"
        )) {
        element.style.display = "none";
    }
}
<div id="someid" class="someclass">
    <div class="title">Should Show</div>
    <ul>
        <li style="display: none;">1</li>
        <li style="display: none;">2</li>
        <li style="display: none;">3</li>
        <li>4</li>
    </ul>
    <div class="title">Should Hide</div>
    <ul>
        <li style="display: none;">1</li>
        <li style="display: none;">2</li>
        <li style="display: none;">3</li>
        <li style="display: none;">4</li>
    </ul>
    <div class="title">Should Show</div>
    <ul>
        <li style="display: none;">1</li>
        <li>2</li>
        <li style="display: none;">3</li>
        <li style="display: none;">4</li>
    </ul>
</div>

You might factor out at least the every calback (and possibly the whole check):

const isDisplayNone = (element) => getComputedStyle(element).display === "none";
for (const element of document.querySelectorAll(".title")) {
    if (element.nextElementSibling.tagName === "UL" &&
        [...element.nextElementSibling.children].every(isDisplayNone)
       ) {
        element.style.display = "none";
    }
}
<div id="someid" class="someclass">
    <div class="title">Should Show</div>
    <ul>
        <li style="display: none;">1</li>
        <li style="display: none;">2</li>
        <li style="display: none;">3</li>
        <li>4</li>
    </ul>
    <div class="title">Should Hide</div>
    <ul>
        <li style="display: none;">1</li>
        <li style="display: none;">2</li>
        <li style="display: none;">3</li>
        <li style="display: none;">4</li>
    </ul>
    <div class="title">Should Show</div>
    <ul>
        <li style="display: none;">1</li>
        <li>2</li>
        <li style="display: none;">3</li>
        <li style="display: none;">4</li>
    </ul>
</div>


With tomorrow's CSS, we can do it if we change the structure slightly to add a wrapper div and we hide the li elements with a class rather than inline styling (which is almost always best anyway):

.hidden {
    display: none;
}
.container .title {
    display: none;
}
.container:has(li:not(.hidden)) .title {
    display: block;
}
<p><strong>Only works on cutting-edge browsers!!</strong></p>
<div id="someid" class="someclass">
    <div class="container">
        <div class="title">Should Show</div>
        <ul>
            <li class="hidden">1</li>
            <li class="hidden">2</li>
            <li class="hidden">3</li>
            <li>4</li>
        </ul>
    </div>
    <div class="container">
        <div class="title">Should Hide</div>
        <ul>
            <li class="hidden">1</li>
            <li class="hidden">2</li>
            <li class="hidden">3</li>
            <li class="hidden">4</li>
        </ul>
    </div>
    <div class="container">
        <div class="title">Should Show</div>
        <ul>
            <li class="hidden">1</li>
            <li>2</li>
            <li class="hidden">3</li>
            <li class="hidden">4</li>
        </ul>
    </div>
</div>

That will only work on cutting-edge browsers such as Chrome v105 and higher (not even Chrome v104 has it). More about :has here on MDN and in the spec.

T.J. Crowders "tomorrow" CSS using :has does not require changing the layout - only replacing inline style with a class (as in his answer) - but that's all that needs to change

Note: this works in Chrome and Edge (not sure about Safari) - in Firefox this does not work even with the :has config flag enabled - Firefox doesn't like the sibling selector (i.e. +) in :has

.hidden {
  display: none;
}

.title:not(:has(+ul>:not(li.hidden))) {
  display: none;
}
<div id="someid" class="someclass">
  <div class="title">Should Show</div>
  <ul>
    <li class="hidden">1</li>
    <li class="hidden">2</li>
    <li class="hidden">3</li>
    <li>4</li>
  </ul>
  <div class="title">Should Hide</div>
  <ul>
    <li class="hidden">1</li>
    <li class="hidden">2</li>
    <li class="hidden">3</li>
    <li class="hidden">4</li>
  </ul>
  <div class="title">Should Show</div>
  <ul>
    <li class="hidden">1</li>
    <li>2</li>
    <li class="hidden">3</li>
    <li class="hidden">4</li>
  </ul>
</div>

Related