CSS selector for empty or whitespace

Lots of people missing the point of this question, which I've addressed in the following exposition, but for those just looking for the answer, I'm mirroring the last paragraph here:

Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.


While the question depicts a <p> element containing a handful of regular space characters, which seems like an oversight, it is far more common to see markup where elements contain only whitespace in the form of indentation and blank lines, such as:

<ul class="items">
  <li class="item">
    <div>
      <!-- Some complex structure of elements -->
    </div>
  </li>
  <li class="item">
  </li> <!-- Empty, except for a single line break and
             indentation preceding the end tag -->
</ul>

Some elements, like <li> in the above example as well as <p>, have optional end tags, which can cause unintended side effects in DOM processing as well in the presence of inter-element whitespace. For example, the following two <ul> elements don't produce equivalent node trees, in particular the first one does not result in a li:empty in Selectors level 3:

li:empty::before { content: '(empty)'; font-style: italic; color: #999; }
<ul>
  <li>
</ul>
<ul>
  <li></li>
</ul>

Given that HTML considers inter-element whitespace to be transparent by design, it's not unreasonable to want to target such elements with CSS without having to resort to modifying the HTML or the application generating it (especially if you end up having to implement and test a special case just to do so). To that end, Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.


@BoltClock provided a fantastic answer to this question, showing that this (currently, that is, working with CSS Specification 3) cannot be achieved by CSS alone.

@BoltClock mentioned that elements that are truly empty (which is a weird definition as explained) can be targeted by using the pseudo selector :empty. This pseudo selector is only available in CSS 3 and WILL NOT select elements that have only whitespace as content.

@BoltClock stated that the only way to clean up elements that have only whitespace as content is to fix the HTML, but that is not entirely correct. This can also be achieved through the implementation of Javascript.

KEEP IN MIND! The Javascript that I am offering to solve this issue may take a very long time to execute, so the best method is to clean up the raw HTML instead if possible. If that is not possible, then this may work as a solution, as long as you do not have too extensive of a DOM tree.

I'll walk through the steps of how to write the script yourself...

First of all, launch everything after page load.

This should be pretty obvious. You need to make sure that the DOM has fully loaded before running your script. Add an event listener for page load:

window.addEventListener("load", cleanUpMyDOM);

...and, of course, before that, create a function called cleanUpMyDOM. We will write the rest of our logic within this function.

Second, gather the elements that we are checking.

In our example we are going to check the entire DOM, but this is where our script can get VERY extensive and may make your page unresponsive. You may want to limit the amount of nodes you are iterating over.

We can grab the nodes in question by using the document.querySelectorAll. What's nice about this function is that it will level out the DOM tree and we won't have to recurse the children of each node.

var nodes = document.querySelectorAll("*");

As I said earlier, this code will grab EVERY DOM node, and that is probably NOT a good idea.

For example, I am working with WordPress, and some of the internal pages have some junk in them. Luckily, they are all p elements that are children of a div.body element, so I can change my selector to document.querySelectorAll("div.body p"), which will select only p elements that are children of my div.body element recursively. This will greatly optimize my script.

Third, iterate the nodes and find the empty ones.

We'll create a loop for the nodes array and check each node in it. We will then have to check to see if the node is empty. If it is empty, we'll apply a class to it called blank.

I'm just shooting from the hip here, so if you notice a mistake in this code, please let me know.

for(var i = 0; i < nodes.length; i++){
    nodes[i].innerHTML = nodes[i].innerHTML.trim();
    if(!nodes[i].innerHTML)
        nodes[i].className += " blank";
}

I am sure that there is a cleaner way to write the loop above, but this should get the job done.

Lastly, all you need to do is target the blank elements with your CSS.

Add this rule to your stylesheet:

.blank {
    display:none;
}

And there you have it! All of your "blank" nodes have been hidden.

For those who just want to jump ahead, here is the finished script:

function cleanUpMyDOM(){
    var nodes = document.querySelectorAll("*");
    for(var i = 0; i < nodes.length; i++){
        nodes[i].innerHTML = nodes[i].innerHTML.trim();
        if(!nodes[i].innerHTML)
            nodes[i].className += " blank";
    }
}
window.addEventListener("load", cleanUpMyDOM);

Once again, if you notice any issues with my code, please let me know in the comments below.

Hope this helps!

P.S. Many people may be wondering why you would want to do this, as it does feel like bad practice. I would avoid doing this, but I am in a situation where I am starting to consider it. The content of the pages on my site are created through a WYSIWYG editor. This content is created and modified constantly by the marketing team and I get pretty overwhelmed handling the support for their slip-ups. Its not my job to fix WordPress's WYSIWYG editor (nor would I ever want to), but I could write a very simple script that can handle some of the work for me. That definitely seems like the better answer to me, besides training the support team on managing their whitespace when making edits.