CSS Qualified Selectors

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

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:

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 excerptis 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 archiveul 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?

Also known as “parent selectors”. Proposed and disposed many times in both the WG and the www-style mailing list over the last many years. The objections always seem to come from implementors and people who do a lot with the DOM spec. Something about the sanctity of one-pass algorithms or fear of circular references or some such.

@Eric, is there a “proposal graveyard” somewhere to save relative newbs (like myself) from wasting their breath? (I swear I googled for “parent selector” before writing this a couple days ago and didn’t find anything—it even shows up in my Safari Google bar’s list of previous searches—but now there’s tons of related results).

As for circular references and one-pass algorithms, I don’t understand implementors reservations (especially those related to performance) when the feature can be implemented easily in an interpreted language like JavaScript.

This would indeed be very useful. Especially for form-handling. By using qualified selectors it might be possible to write smaller css files and markup because we would not have to use as many classes as we do now.

Go ahead and give the W3C headquarters a call so they can implement this by tomorrow …

Ah, this indeed is something I’ve been wanting (and have discussed with some colleagues before) for a long time as it’s extremely useful. Most of all the discussion/idea came above water when wanting to “border-bottom all a elements, but don’t apply a border to them if they contain an image” (cfr. your intro example).

Hoping this will ever get implemented, though - as Eric said - I think it’s really hard to to implement as (this is how I see it) when looping through all elements to apply the CSS, a second loop for each looped element would be needed (in the opposite direction) which would only exponentially increase the load to render the CSS.

This is definitely something I’ve wanted many, many times since I started working with CSS. I think your implementation and syntax seem generally sound, too. Sounds like (from Eric) the powers that be are disinterested, though — bummer.

Ya, bummer indeed, as I can identify with practically everyone else on here who has come across a few dozen uses for this.

Especially as browsers feel increasingly bloated (I’ve got no empirical evidence that they are onhand, but they sure feel like it), I can’t see why this would really create any appreciable performance issue. But, as Eric said, from being on W3C lists, I’ve observed a number of instances where interesting ideas were driven into the deadpool with the same single-pass rationale.

This is a nice idea and it’s a shame that it’s already been put to one side. In some ways though, it doesn’t even go far enough.

Why is it that there’s a wonderful way of selecting parts of XML documents exposed through XPath and yet CSS only has a limited set of descender, sibling and pseudo selectors? As far as selection goes, they’re basically the same and I think the things that you could do solely with CSS would go through the roof if it had a full XPath (or equivalent) implementation.

For example, imagine being able to use something like the following XPath expression to select the legend of fieldsets that contain any inputs marked with an “error” class, regardless of how many other elements sit between the fieldset and the input:

Although proposed numerous times before in different variations I keep hoping something like this will make it into CSS someday.

Using < for a qualified selector, where the first part of the qualifier can represent any descendant, is maybe a bit confusing as > stands for child selector (only first level descendant).
Therefor, maybe it’s better to use < for qualified child selectors (qualifier starts with a child). And perhaps ^ could then be used for qualified descendant selectors.

I can certainly see the issue with implementing this from the perspective of an implementor (ie: a browser developer). The entire contents of an element must be loaded before a parent element can be styled. Likewise, any and all child elements now have to wait to be loaded to see if any parent styles that are changed affect those child elements. In cases, an HTML page must be loaded in entirety just to determine what styles can be applied where (which is essentially what the JavaScript-based solutions do).

It’s not like redraws aren’t happening already though—an oversized child image, table or white-space: nowrap; element affects the width of any number of ancestor elements.

And isn’t it already possible for content authors to insert inline <style> elements outside of the <head> that then require previously rendered elements to be redrawn?

This isn’t that different. But with qualified selectors (or any of the previously suggested parent selector implementations—I really dig the :has pseudo-selector) the parser has the benefit of knowing which elements are likely to require a redraw so implementors could a.) choose to delay the drawing of the element (as they do when encountering external scripts or CSS) or b.) flag the element for redraw if required by child elements.

It’s not like every single element will have to be redrawn. And the one’s that do won’t come as a surprise to the parser.

I can understand an implentor’s reservations in 2002 when memory and CPU cycles were scarce by today’s standards, but now? How about in ten years when the current generation of browsers are considered legacy? Previously intractable problems just beg to be re-evaluated—especially in computing.

This would be definitely be very handy, Shaun. As you say, (a lack of) computing power shouldn’t be a barrier to including this sort of stuff into CSS specs - as other commenters rightly say, by the time it’s been implemented the power will be there. And with John having implemented it already in JQuery, surely that argument is null and void. Bring it on!

Ok. So what am I missing? My CSS-foo is bordering on none. In fact, if CSS were a weapon of some kind, I’m fairly certain my license would have been revoked years ago.

But I’m trying to wrap my head around what you said about images, that you don’t want the a style to effect, would need their own class? That the only solution, rather, would be to have these qualified selectors if you didn’t want to add classes to images?

Am I reading this right? I’m guessing that I’m not. And I’m guessing that I’m missing the point (in that example specifically).

I worked up a quick test page to see if something weird happened if I just cascaded the style and wrote a img { border: none; } to remove the border on images that were wrapped in a.

Your a img {border: none;} isn’t actually doing anything (removing it illustrates as much). Add some text to the image link and you can see that the border is still there or you can set the opacity of the image to 0.5 and you’ll see the border is actually just behind the image (probably something to do with vertical alignment and inline-block elements). What if you were using a transparent gif or png? Border-bottom might be a bad example. With padding or a background property the impact on images isn’t so coincidentally hidden.

I just recently implemented more of the CSS3 selectors in WebKit, and it took a really large amount of work and multiple iterations to make the HTML5 spec viewable again after adding support for dynamic nth-child, last-child and sibling selectors. The spec literally became unviewable in WebKit for a while as I worked to optimize and optimize those selectors to try to get back the performance lost from adding support for dynamism.

In the end, I got back the performance, but at the cost of bloating memory.

If you think a parent selector can’t render pages unusable in 2008, you are wrong. It can quite easily. The problem with this selector is that badly written rules could lock up a browser. An additional concern is that authors would use these rules just for alternative browsers (leaving IE to perform well and degrading Opera/Safari/Firefox performance).

I think you do not fully appreciate what it means to have to support selectors in a dynamic environment.

I think you do not fully appreciate what it means to have to support selectors in a dynamic environment.

I’m quite certain that I don’t Dave. That’s why I asked for and appreciate browser developer insight. Can you give us an example of how the selector might be abused? Something as simple as body < a:visited?

That you have recent experience re-evaluating the problem is good enough for me, we’re just trying to get a better understanding of the problem.

The problem is that browsers cannot store all of the information they need to know exactly what to invalidate when something in the DOM changes. They end up using a tainting model that inevitably (in the worst case scenarios) leads to over-invalidation.

For example, consider the rule:

.foo div { }

Now imagine some gigantic document with lots of divs sprinkled all over the place. Somebody then goes and sets the class name of the body to foo.

In WebKit, that will result in us essentially having to re-resolve style on the entire document. For a large page, that example could lock up the browser.

To expect that WebKit would know exactly how many divs were inside the body and where they are in order to do precise invalidation is unrealistic, since that would consume too much memory (especially as you started piling on arbitrarily complex rules).

The problems can get even worse when you chain selectors together.

.foo ~ .goo div { }

If some element suddenly acquires the class name foo, you have to examine every single following sibling and all of their descendants.

These sorts of cases are reasonably uncommon with forward chaining selectors (or at least the author scopes them reasonably well when they do occur).

With parent selectors it becomes extremely easy to accidentally cause a document-wide grovel. People can and will misuse this selector. Supporting it is giving people a whole lot of rope to hang themselves with.

The sad truth about CSS3 selectors is that they really shouldn’t be used at all if you care about page performance. Decorating your markup with classes and ids and matching purely on those while avoiding all uses of sibling, descendant and child selectors will actually make a page perform significantly better in all browsers.

For example style information can actually be shared across siblings and even cousins in a DOM tree if you know no sibling rules are used. WebKit typically achieves a 75% memory reduction in style footprint on pages that don’t make use of sibling selectors.

Descent into children can be avoided when changes happen if you know no descendant rules are used. Dynamic effects like :hover will perform better if descendant rules are avoided.

In many cases these rules can still be used and the performance will be perfectly fine. This is especially true on small blog pages that don’t get that large. However once you scale up (e.g., the HTML5 specification), the problems become more apparent.

Decorating your markup with classes and ids and matching purely on those while avoiding all uses of sibling, descendant [emphasis added] and child selectors will actually make a page perform significantly better in all browsers.

Do descendant selectors really fall in that list? I’m not sure this particular topic gets talked about often enough. What’s the impact of each type of selector on rendering performance? Eg. what kind of effect does a selector like ol ul li have on performance? Is this documented somewhere?

The true utility of CSS—to me—is establishing context with a parent element and then styling its descendants based on that context, thereby minimizing repetitive and redundant presentational hooks in the markup. But from what you’ve said, it sounds like I’m “doing it wrong.”

There’s a trade-off between markup complexity and style resolution complexity. Where you want to stand depends on what you’re trying to do. If you’re writing a reasonably-short static document, you don’t lose much performance and you gain leaner, more maintainable markup by using more sophisticated selectors. If you’re writing a highly-dynamic web app (or, in the case targeted above, browser UI) then you’re better off using heavier markup so you can use dead-simple selectors.

Thanks fantasai, that provided a lightbulb moment. At first, this seemed backwards to me:

The style system matches a rule by starting with the rightmost selector and moving to the left through the rule’s selectors.

But when trying to apply styles while rendering the partial page as it is served to the browser it makes perfect sense. There’s no need to re-query the partial document when the renderer encounters a potential target for a style—it already has its target, it just needs to (if you’ll excuse the expression) qualify it by examining its attributes and ancestors.

Is this same logic used for redraws? It seems like the right-to-left approach would be less efficient once the DOM is already constructed.

I think the disbelief (and grief) that implementors get from designers and developers when this type of selector is brushed off is due to diametrically opposed mental models of how CSS selects its targets.

Because we read and write left-to-right, we assume the parser does as well. That gives the impression that the parser would look at div a and think (as we do) “I need to grab all divs, then grab any as in those divs and apply this style” when (please correct me if I’m misunderstanding this) the truth is more like “I need to grab all as and then release any that aren’t an ancestor of a div.” We are more accustomed to interacting with a fully-realized DOM (thanks to your hard work up front).

@fantasai. Is there a difference between parsing for the browser UI and a web page or web app? The recommendations in the Mozilla doc seem to imply that using the cascade is overly expensive, especially with type selectors, and thus bad to use. All the CSS books and tutorials I’ve ever seen use descendant selectors and type selectors along with class and ID selectors.

I favor other idea which was proposed on CSS mailing list not so long ago - it’s indication which simple selector elements we want query to return:
e.g.
document.querySelectorAll(“!p a”)
will return all p elements that has anchor descendant.
Additionaly introduction of ‘scope’ pseudo class would resolve all other issues. e.g.
el.querySelector(“!* + :scope”)
returns previous element sibling element of el

I think it’s cleaner more powerfull and it doesn’t introduce new combinators.