Debugging HTML With Advanced CSS Selectors

Writing HTML is hard. At least writing semantically sound, valid HTML is. This might come as a surprise to those who only scratch the surface of what HTML really can do. What can be so hard about a few elements, right? At least it isn’t an object-oriented, multi-paradigm programming language. But as soon as you start to dig deeper you realize that there is a lot to get wrong in HTML.

For one, HTML now contains hundreds of elements. This alone can make it hard to find the right element for the job. But also if you know all the vocabulary, there are many more things to consider. For example, which elements are allowed to be nested within other elements or which attributes a certain element can have. Not to forget accessibility: If we use semantically correct elements, we make sure that our content will be understood by assistive technologies like screenreaders and that our interfaces are usable for everyone. Using a native button element instead of a div when you need a button will not only impress your friends but also add standard styles and accessibility features like focus and additional keyboard events.

So there are a lot of reasons why you should write semantic, valid, accessible HTML. But how do you make sure that your code is error-free? If you are working on a larger team or with a team of content managers, it can be a challenge to maintain a high standard, especially if different team members have a different experience level with HTML.

You can, and this would definitely be the best solution, set up a thorough testing environment where each HTML or accessibility error is flagged immediately. Truth be told, we are far from testing being a priority – or even a sphere of knowledge – for many developers and clients. Also, when you are in an earlier phase of a project, and you are, for example, still prototyping in HTML or building basic HTML templates, you might not yet have an automated testing solution in place.

So how can you still make sure that your HTML is valid? Testing your HTML manually with the W3C Markup Validation Service would be another option. But this can be tedious, too. Wouldn’t it be nice to be able to see HTML errors directly in the browser? This is where CSS comes in. By the very nature of CSS, you are able to filter for elements in the most remote corners of the DOM. And, as it turns out, you can also use it to, at least visually, validate your code by highlighting elements that contain errors.

Adam Argyle posted an example of how this can look like on Twitter the other day:

/* CSS */
:is(ul, ol) > *:not(li) {
  outline: 2px dotted red;

In this case, the selector matches all elements that are inside of lists but not a list item li and displays them with a red outline. An outline makes sense because, other than using border, it does not change the size of the box of the element. Adam uses the new :is() pseudo-class, which still has limited browser support. But you could easily write it without :is():

/* CSS */
ul > *:not(li), ol > *:not(li) {
    outline: 2px dotted red;

This technique can be used for many more useful things, for example, detecting images without an alt attribute:

/* CSS */
img:not([alt]) { ... }

As a general rule, images should always have an alt attribute to be accessible. When the attribute is set, a screen reader will read the alternative text. And when it is empty, the screen reader will ignore the image. If there is no alt attribute at all, though, the screen reader will read the src attribute instead. Not very useful in most cases.

Ire Aderinokun has written a great post about many more use cases for listing HTML with CSS, like checking for empty interactive elements, unlabelled form elements, or faulty or missing link targets:

/* CSS */
a[href*="javascript:void(0)"] { … }

She even created a Chrome extension that combines many of those selectors into a style sheet that you can apply to any web page to check the HTML for accessibility issues.

And there are many more interesting things you could do. For example, checking for the previously mentioned div buttons from hell:

/* CSS */
div[role="button"] { 
  text-decoration: blink; 
  /* Don't actually use blink here, it's a joke. */

Or, if you don’t shy away from using gigantonormous selectors, you could check if a span only contains the allowed “phrasing content”, or if some div lover put a lovely div in your span:

/* CSS */
span > :not(abbr):not(audio):not(b):not(bdo):not(br):not(button):not(canvas):not(cite):not(code):not(command):not(data):not(datalist):not(dfn):not(em):not(embed):not(i):not(iframe):not(img):not(input):not(kbd):not(keygen):not(label):not(mark):not(math):not(meter):not(noscript):not(object):not(output):not(picture):not(progress):not(q):not(ruby):not(samp):not(script):not(select):not(small):not(span):not(strong):not(sub):not(sup):not(svg):not(textarea):not(time):not(var):not(video):not(wbr) {
  outline: 3px dashed red;

Using CSS to debug all of your code covering all possible ways HTML in which elements could be misused would certainly be overkill. But depending on your project, this technique could still be quite useful to work against the most common and most severe mistakes like missing links or alt attributes. One could even think of having a default debug.css file that contains a standard set of the most useful rules. I think I’ll try that in one of the next projects.


This is the 24th post of my 100 days of writing series. You can find a list of all posts here.


8 Webmentions

Photo of Kevin Cunningham
Kevin Cunningham
This is really interesting - found it in a longer article from @m_ott (…) which talks about wider debugging of semantic HTML using CSS. Great stuff!