I’m considering writing a proposal for CSS Qualified Selectors to be submitted to the CSS Working Group. But before I do I wanted to open the idea up for discussion. I’m looking for comments on the proposed feature’s utility and hopefully, offers to help write the implementation details, specifically changes to CSS Grammar and Lexical Scanner—I’m pretty sure I can handle the rest.

What exactly are Qualified Selectors?

Descendant selectors are a pretty common sight so let’s start there.

a img { ... }

This style is applied to any image inside of any anchor element. But what if we already have an anchor style defined

a { text-decoration: none; color: #A10; border-bottom: 1px dashed #A10; }

but don’t want this style applied to our image links? Currently we must add a new class to each of our image links so we have a hook to disable the unwanted style (in this exampe, the border-bottom ).

What if we could just write:

a < img { border: none; }

That is a qualified selector. A qualified selector can only contain one < . The element immediately to the left of the < is the target of the selector, everything after is the qualifier. In this case, the style would only apply to anchors that contain an image.

Here’s another example: since most modern browsers allow you to click on a label to check the corresponding checkbox we can hint at this behavior by changing the cursor style:

label { cursor: pointer; }

But what if the contained checkbox is disabled? The pointer cursor is misleading (since the label’s checkbox can’t be un/checked). Currently we need to manually class the label (or disable an additional arbitrary parent element) to correct this. With Qualified Selectors (combined with attribute selectors) we could just write:

label < input[disabled] { cursor: default; }

One last example: let’s say our site has news, blog and event archives. The archive is a list of items containing an excerpt and a link to the full article or event details. The mark-up looks something like this:

<ul class="archive"> <li> <h3><a href="#archive-link">Headline/Event Title</a></h3> <div class="excerpt"> <!-- excerpt which may contains block level elements and other links --> </div> </li> <!-- and many more --> </ul>

Suppose we want to differentiate entire previously visited items (rather than just previously visited links)? There is no way to do this currently (without being overly clever with server- or client-side scripting). With qualified selectors it would be as easy as:

ul.archive li < h3 a:visited { opacity: 0.5; }

As you can see above, both sides of the < can contain complex or compound selectors. Again, everything before the < selects the target, and everything after selects the qualifying element (in the case above, a visited link in an h3 in our target list items in archive unordered lists). If the link in the h3 has not been visited, the li appears at the default, full opacity. Likewise even if a link contained by the excerpt is visited, the li will still appear at full opacity.

How would this be implemented?

Based on my admittedly limited DOM-based CSS selector parser experience, the changes required for this type of selector would appear to be minimal. Most CSS parsers work from the top down with each successive step deeper into a descendant selector replacing the previously selected ancestor elements until they have selected the target elements. With qualified selectors rather than subsequent descendants replacing the ancestor elements, the descendants would be used to eliminate unwanted ancestors.

Let’s say we have a recursive parser() function that takes two arguments, a selector string and the parent node(s) (if no parent is provided it defaults to the root html element). Let’s call parser('ul.archive li h3 a') to see how it works.

Since a second argument isn’t provided the parser assumes that the root html element is the parent . It then “snaps off” the first individual element selector (in this case ul.archive ) and saves the remainder of the selector ( li h3 a ) in a remainder variable for later use. It proceeds to select all ul in the parent and eliminates any without an archive class. A reference to each matching ul is stored in a selected variable. Once all ul have been considered the parser() function calls itself. This recursive call is provided the non-empty remainder of our original selector and the currently selected elements as arguments (the selector and parent respectively). The result of the recursive call replaces the currently selected elements. This repeats until no remainder remains and the current value of selected (containing just a elements) is returned.

Qualified selectors would require modified logic around the recursive call. Let’s call parser('ul.archive li < h3 a:visited') to observe the changes. The function proceeds as before until the remainder equals < h3 a:visited . When the remainder begins with < that signifies that the currently selected elements are our actual targets and that any of those elements that don’t contain the elements selected by the remainder should be eliminated. So at this point the parser removes the < from the beginning of the remainder and calls itself, like so parser(remainder, selected) ( selected contains all matching li ). Rather than replacing selected with the result of that recursive call as before, the result is stored in a qualifiers variable. Then for each selected it loops through qualifiers , eliminating any selected that do not contain one of the qualifiers (by checking each qualifiers ancestors tree for each selected ). selected is returned containing just li elements contained by an archive ul that contain a visited link in an h3 .

Sound good? Sound Off

Designers and developers, does this sound useful? When creating mark-up and style I prefer to write selectors based on context (rather than classing every. single. element). Usually that context is created with one or two classes on a parent element. Qualified selectors would help eliminate the need for some of those additional classes and provide previously unavailable distinctions related to states already maintained by the browser or elsewhere in our mark-up.

Browser developers, is this even feasible? Would the syntax of this new selector degrade gracefully (read: be completely ignored) in current and legacy browsers? If so, would you be interested in helping translate the proposed syntax into the required changes to CSS Grammar and Lexical Scanner?