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
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?
048 Comments
As I was writing this I realized that the opposite might also be useful: being able to select only elements that don’t contain other specific elements.
Imagine using something like:
to hide an empty
excerpt
.As a developer, I’d find those EXTREMELY handy. I can’t count how many times I’ve had to toss an extra class name onto an element just to get around the issue that what you’re proposing would solve.
I think this would be extremely useful. This would allow for much cleaner logical code. I support it.
I’ve wanted this for at least five years. In fact, I think we even talked about it once.
Unfortunately, you could probably make it happen quicker by hacking together a javascript/DOM based method which accomplished something similar.
Hack = A few weeks
Writing spec, having spec approved, having browser makers adopt it = 10 years
:(
@Mike D: I’ve already written a DOM-based equivalent—it only took an hour. But it has the same drawbacks as other, similar hacks: FOPSC, additional processing/downloading overhead, configuration.
Hacking and waiting are both drags. But if we can eliminate one hack after 10 years, that’s a good thing (and unlike embedded typefaces this won’t have to deal with content-creator licensing issues).
Excellent article.
I hope they actually implement this as it’s a sound idea.
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.
More’s the pity.
This would be incredibly useful. I don’t know much about the technical side of things so I can’t really argue for this as far as how it could be implemented, but on a conceptual level I love it.
@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.
There’s a post on www-style from May 2002 that covers the various proposals for this use case, along with the advantages and disadvantages of each.
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.
The powers that be are interested, they’re just constrained by practical limitatations.
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.
I was really into this, and subsequently a little bit heartbroken after reading Eric’s comment.
A real shame, I think a lot of developers have been clamoring for a Parent, or Qualified, selector, or at least some kind of psuedo-class…
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:
fieldset[//input/@class='error']/legend
Definitely agree with Jim’s comment above. Qualified selectors sound nice, but if anything, add XPath support to CSS!
Justing adding my vote. This would be an incredible addition to the spec.
John Ressig just posted a solution to this for jQuery
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.
So in…
the selector…
would then select both links, whereas…
would only select the first.
A not/except selector would be handy in combination with any other selector I guess, not just with qualified selectors.
For example:
would select all unordered lists, except those with a className of “nav”, and…
would select all empty divisions and those that contain only plain text.
Instead of
.excerpt:empty
? :) Also, remember that you have:not()
, which will finally be added to Opera in it’s next release.@Jim
fieldset input.error ~ legend
‘works’ if you place the legend last.@Key: you’re right, forgot about the upcoming CSS3 pseudo-class 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’d still be nice to have, though. :)
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.
“You is talking loco and I like it.” ~ Hansel, Zoolander
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.
I’m totally expecting to have tomatoes thrown at me - so feel free and don’t hold back.
So I didn’t read that I needed to use Markdown. So here is that link I mentioned to the test page.
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 to0.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. Withpadding
or abackground
property the impact on images isn’t so coincidentally hidden.Shaun: Ta-da! I’m a (insert favorite n00b descriptor here). I sincerely appreciate the follow-up.
Shaun: Hopefully this is correct. Let me know otherwise.
Close enough for jazz. If I were to take another stab at defining the problem more succinctly I might say:
Thats the **** right there. Thanks.
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’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:
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.
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.
Thanks for the explanation Dave. This statement gives me pause though:
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.”
See http://developer.mozilla.org/en/docs/Wr … icient_CSS
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:
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 alldiv
s, then grab anya
s in thosediv
s and apply this style” when (please correct me if I’m misunderstanding this) the truth is more like “I need to grab alla
s and then release any that aren’t an ancestor of adiv
.” We are more accustomed to interacting with a fully-realized DOM (thanks to your hard work up front).Forgot to ask, since that page is hosted by Mozilla and written by Dave, I assume this is how both Firefox and Safari work. Is it safe to assume that this is also how IE works?
I like the idea very much, but, as the usual css syntax, the elements are written in a traversing order.
In the example you used
a > img
the>
symbol represents not an arrow, but the descendance of theimg
element on the DOM.Something like the
img
element is inferior (minor) than elementa
.So, the correct syntax, in my opinion, of course, should be:
Wich represents that the
a
element is superior (greater) than elementimg
.This keeps the
a
element on the end of the syntax. Like the descendant selectors we are used to write in CSS.This also looks more close to the javascript or jQuery syntax as well:
Now, about browser performance, I guess it would really be a pain in the ass.
But..
Nowadays, we are already doing this. WITH JAVASCRIPT :( !!
And the designers will continue doing this and many other things that will reduce the browser performance more and more.
Allowing us to do that providing the best method the browser can achieve (performatically) via a CSS syntax should standarize the way we do things like that.
This should improve the actual performance since we already do this by worse ways.
Another reason for using the syntax
img < a
is that doing this, the syntax should allow us to keep traversing in the CSS code.Something like:
…
This should allow us to access any level of the DOM hierarchy based on any selector.
@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’ve been yearning — and I mean yearning — for this for so long, especially the
!<
part.Let’s hope that all the heavy hitters get on board and push this through!
Holding breath until jquery, prototype or some other JS lib community implements the “impossible”.
Hell, if they can implement most of the CSS 2/3 selector stuff in javascript before Microsoft could implement some of it for IE 7, I wouldn’t be surprised.
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.