Why we don't have a parent selector

On a seemingly regular basis, I see this discussion come up as to whether CSS should have a particular feature like the parent selector and while I haven't worked on a browser engine, I have my theories.

In short: performance.

How CSS gets evaluated

With work, I've had to do quite a bit of examination of performance. We run a number of tools over an application to determine where the bottlenecks are. One such application is Google Page Speed which provides a number of recommendations to improve JavaScript and rendering performance. Before I get into its recommendations, we need to understand a little better about how browsers evaulate CSS.

The style of an element is evaluated on element creation

We often think of our pages as these full and complete documents full of elements and content. However, browsers are designed to handle documents like a stream. They begin to receive the document from the server and can render the document before it has completely downloaded. Each node is evaluated and rendered to the viewport as it is received.

Take a look at the body of an example document:

<body>
   <div id="content">
      <div class="module intro">
         <p>Lorem Ipsum</p>
      </div>
      <div class="module">
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum <span>Test</span></p>
      </div>
   </div>
</body>

The browser starts at the top and sees a body element. At this point, it thinks it's empty. It hasn't evaluated anything else. The browser will determine what the computed styles are and apply them to the element. What is the font, the color, the line height? After it figures this out, it paints it to the screen.

Next, it sees a div element with an ID of content. Again, at this point, it thinks it's empty. It hasn't evaluated anything else. The browser figures out the styles and then the div gets painted. The browser will determine if it needs to repaint the body—did the element get wider or taller? (I suspect there are other considerations but width and height changes are the most common effects child elements have on their parents.)

This process continues on until it reaches the end of the document.

Here is a visualization of the reflow/repaint process in Firefox:

CSS gets evaluated from right to left.

To determine whether a CSS rule applies to a particular element, it starts from the right of the rule and works it's way left.

If you have a rule like body div#content p { color: #003366; } then for every element—as it gets rendered to the page—it'll first ask if it's a paragraph element. If it is, it'll work its way up the DOM and ask if it's a div with an ID of content. If it finds what it's looking for, it'll continue its way up the DOM until it reaches the body.

By working right to left, the browser can determine whether a rule applies to this particular element that it is trying to paint to the viewport much faster. To determine which rule is more or less performant, you need to figure out how many nodes need to be evaluated to determine whether a style can be applied to an element.

Rules

Going back to Page Speed, let's take a look at a couple of the recommendations it provides:

  • Avoid descendant selectors
  • Avoid child or adjacent selectors

Of course, ID selectors are the fastest. A rule of #content can be evaluated quite quickly for an element being processed. Does it have the ID or not? Class selectors are almost as fast. There are no other dependencies that need to be checked.

Descendant selectors like .content .sidebar are problematic because to determine whether it should apply the styles to .sidebar, it has to find .content. Child selectors (Ex. .content > .sidebar) are better than general descendant selectors because the browser only has to check one other element instead of mutiple elements.

Universal and Tag Selectors

Within its recommendations for descendant, child or adjacent selectors, it says to avoid the universal and tag selectors.

Take a look at the following example:

#content * { color: #039; }

With the ID selector there, your initial thought might be that this is really fast. The problem is that with the browser engine evaluating from right to left, the universal selector matches first. For the browser to detemine whether this element should be this deep shade of blue, it now has to check every ancestor element until it finds an element with an ID of content.

And it'll have to do this for every single element on the page.

Now that we understand when an element gets evaluated, how the selectors are determined, and how it might impact performance, let's look at an example issue.

Why IE took so long to get :last-child support

A common complaint: most browsers these days have support for :last-child—except Internet Explorer. (It'll be in IE9!) One might think, "how much harder can :last-child be if you have :first-child?"

Let's pretend we're a browser and we're parsing that document example I used earlier.

/* The CSS */
.module > p:first-child { color: red; } /* first rule */ 
.module > p:last-child { color: blue; } /* second rule */

As we go into the first div, we see that we have a paragraph. The browser sees something like this:

<div class="module">
   <p>Lorem Ipsum</p>

Should the first rule be applied to this paragraph? Yes, it's a paragraph; yes, it's the first child; and yes, it's the direct child element of an element with the class module.

Should the second rule be applied to this paragraph? It is currently the last element. But we haven't loaded any more elements in, so we're just not sure.

Regardless of how I handle this dilemma, we now have the problem of having to re-evaluate the styles for two elements for every new element that we add to the DOM. If we add another paragraph after the first, then we have to re-determine what styles should be applied to the one previous to that.

How do browsers actually handle this?

I wasn't entirely sure how each browser would handle parsing :last-child, so I put together some test cases:

The first example should be very unexciting. In every browser, including IE9, things load up and appear correctly. Within the div, the first element is red and the last is blue. Take a look at the second example, however, and you'll see some interesting behavioural differences between the browsers.

The second example pauses before and after every paragraph in the div.

In Firefox, the first paragraph is initially rendered blue. When it loads the second paragraph, the first turns to red and the second paragraph is blue. Finally, when the third paragraph is loaded, the second one turns to the browser default and the third one turns to blue. Firefox treats the last element loaded as the last element until it gets a new one.

In Safari, Chrome and Opera, we see something slightly different. The first paragraph is red. The second one is rendered black. The last paragraph is rendered black until the closing div tag is received a second later. At which point, the last paragraph turns blue. These browsers don't treat any element as the last element until it closes the parent element.

In Internet Explorer 9 beta, I've discovered an interesting bug. While the static page loads correctly, the forced-pause version ends up with an interesting side effect. The first paragraph is blue, then the second paragraph is blue and then the third. Once the closing div is loaded, the second to last paragraph is changed to default black. IE9 is trying to behave like Webkit and Opera but...well...fails. Time to file a bug report with Microsoft.

Why don't we have a parent selector?

That was a fair amount of explanation to get to the original question. The problem isn't that we couldn't have a parent selector. The problem is that it would introduce a performance concern when it comes to determining what CSS rules apply to a given element. If Google Page Speed doesn't like universal selectors then you can guarantee that a parent selector would end up at the top of the list, far exceeding any performance issues you might have with universal selectors.

Let's take a look at why. First off, let's come up with an example syntax for our parent selector.

div.module:has(span) { color: green; }

The problem is that we can't evaluate this rule until either we match the criteria or until all elements within the node are loaded. Related to that, we have to evaluate this and all other rules (in case of specificity issues) that apply to this element for each descendant element that we load.

Looking at a chunk of our document:

<div class="module">
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum <span>Test</span></p>
</div>

Based on what we've seen, the module would get rendered without the styles from the parent selector coming into play. When the first p element gets loaded, it'll have to re-evaluate to see if the parent selector applies to the div. It'll do this again for the next paragraph. And again for the third. Finally, when it loads the span, the parent selector will apply to the parent div and the element will get re-rendered.

Now what? If it changes any inheritable CSS properties, every single child element needs to be re-evaluated and re-rendered. Ouch.

Why can JavaScript solve this problem but CSS can't?

It's a bit of an illusion that JavaScript solves the problem. Generally speaking, JavaScript polyfills or regressive enhancements (or whatever you kids call it these days) only run once after the DOM has completely loaded.

To truly behave the way CSS does, any script that solves these problems would have to run after every single element was rendered to the page to determine whether a polyfill would need to be applied. Remember CSS expressions in Internet Explorer? There's a reason why they were a performance issue.

Not Impossible

Will we ever get a parent selector? Maybe. What I've described is not technically impossible. In fact, quite the opposite. It just means that we'd have to deal with the performance implications of using such a feature.

Published October 10, 2010
Categorized as HTML and CSS
Short URL: https://snook.ca/s/984

Conversation

37 Comments · RSS feed
Tony Milne said on October 10, 2010

I've been aware of writing optimised jQuery selectors and caching them to variables for quite a while now, but never realised the affect my CSS selectors could be having on page load and overall performance - thanks for the insight Snook.

Rudie said on October 10, 2010

Someday, when performance isn't an issue anymore. Exciting things get less exciting everyday. So in a few years (+ a few years for IE) we'll have parent (pseudo) selectors.

Rudie said on October 10, 2010

Which is less efficient:
* { color:black; }
or
#content * { color:black; }
I'd say the first is VERY easy for a browser to paint... Or is that one shameful as well?

Stephanie (Sullivan) Rewis said on October 10, 2010

Thanks for writing the thoughts you explained to me on IM the other day. :) Great explanation! And yes, I've given up my desire (currently) for a parent selector. I'm also rethinking how I write CSS. OOCSS is looking better and better all the time.

Sean Curtis said on October 10, 2010

Why couldn't the parent selector work when the tag is closed like the :last-child one does in Safari/Chrome/Opera? Eg first P is rendered, 2nd one, third one with span, div is closed, checks for span/last-child/whatever-else, applies styles?

Jonathan Snook said on October 10, 2010

@Rudie: #content * is less efficient than *. The reason is because with just a plain *, only one element is evaluated to determine if it matches the selector. With #content *, every ancestor of the element being evaluated has to be checked to see if it has an ID of content applied to it.

@Sean Curtis: There are various ways it could decide to handle it, all with various pros and cons. It could not render the div until all child elements are loaded but that might mean seeing an empty page until the entire document loads. Or, it could wait until the closing element to decide before re-rendering the style but then you still have all the child elements to re-render. That isn't an issue that :last-child has, per se. At least, not on the same scale.

cooljaz124 said on October 10, 2010

Which will load faster - An HTML page with a .css or a page with all css implemented using Javascript/Jquery ?

Keith Clark said on October 10, 2010

Sean, having a parent selector would mean the child nodes would have to monitor their parents for a change to the DOM. What your suggesting would be ok for applying styles during page load but I don't think the initial load time is the big issue - the problem is recalculating styles when the browser needs to redraw the viewport - that's what killed IE's performance when using expressions.

It would be interesting to run speed tests on this stuff but I think it's beyond the scope of JavaScript - it would need to be done during the vendors build/test process.

Brendan Falkowski said on October 10, 2010

The static test renders using Safari/Mac in 5ms. Aside from the great test case for :last-child implementation, I'm not sure there is a great value in CSS performance optimization. Google Page Speed recommends avoiding descendent selectors which seems a little absurd. A robot would suggest using only ID selectors to eliminate specificity.

I'd be curious if 1000 adjacent selectors is slower than 1 extra HTTP request. That's an extreme case, but in the total page speed scheme isn't CSS the least important?

Jonathan Snook said on October 10, 2010

@COOLJAZ12: that depends on whether the JavaScript is applying the same CSS and selectors as there would've been in the beginning. It also depends on when you have to worry about render time. CSS will almost always be faster.

@Brendan Falkowski: It depends on what you're doing. The problem with complex rendering is that it can impact JavaScript performance. It can also affect rendering performance if you're making a lot of repetitive changes—say, with animations. Complex applications like Yahoo! Mail benefit from simplified selectors and improved rendering times because of the amount of work that is being done on the client. If you have a blog then it really isn't an issue because the page is rendered once, with few rules needing to get evaluated. Changing everything to ID or class selectors won't be worth the maintenance headache.

Dmitry Scriptin said on October 10, 2010

From the "philosophical" point of view, :parent selector breaks the concept of CSS in its' "Cascading" part. What :parent does is reversing the flow of rules: while everything go inside and down, it goes outside.

So, I think it's not only the question of performance.

Also I think that the someone's need for this selector is a consequence of his/her misunderstang of CSS basic ideas. We don't actually need this, we can handle everything without it. And it would be bette (IMO) to not have this selector to keep CSS, you know, pure.

Scott said on October 10, 2010

Actually given your explanation of descendent selectors, I don't see how a parent selector could be any worse.

Take '.module:has(span)' - when the browser gets to a span element, all it needs to do is search upwards through the current tree to find .module, same as with the descendent selector. If it finds .module then it can apply the styles to that element right away. Assuming you do not have anything more complex than that simple boolean selector, it should be exactly as fast (or slow) as descendent selectors.

That's not to say a parent selector is a good idea. Developers seem to be very keen to strip HTML to as bare bones as possible, while creating a horrid mess of CSS selectors. Usually adding one simple class can eradicate many lines of CSS.

Jonathan Snook said on October 10, 2010

Scott, the difference is that a descendant selector is only evaluated once when that element is inserted by going directly up the tree. A parent selector would need to be evaluated each and every time a child selector was added. And then once a parent selector was matched, each and every child element would need to be potentially re-rendered. That's a potential for magnitudes of difference when it comes to performance.

For example:

.module span would fail immediately on all of the P's. Only when it hit the span would it attempt to work its way linearly up the tree.

.module:has(span) can't render when .module renders, when it should. Instead, the P gets rendered and the styles on .module get re-evaluated (and fail). Note, we're now checking to see if a rule applies on two elements for every element inserted in the DOM. That's twice as many as before. Once we hit the span, any style on the span renders and then the .module is re-evaluated and then the styles on ALL of the child elements are re-evaluated and re-rendered because of inheritance.

Huge difference.

Arieh said on October 10, 2010

I disagree with your conclusion. Few reasons:
1. A language shouldn't be about performance. Performance is for developers to worry about.
2. The fact that the standard way of doing it is slow doesn't mean it's the way browsers do it. I'm pretty sure browsers can do smart evaluations of rules and determine what is the best way for evaluating them. For example - if a browser sees #content *, it could simply search that #content first. It's doable and feasible, and I doubt that with all the performance wars going on browsers aren't "cheating" a whole lot.

So my point is - CSS is always about how can you best describe the hierarchy of a page. It's up to the browser to worry about performance, and PageSpeed, although a nice tool, always yells at me for very generic uses. The amount of work it would take to add classes to very element I might use is simply non realistic.
As for the parent selector - I agree with Sccot, that it's much more probable that the reason is that it's upside down, and that's counter CSS (although as I understand cascading means that every rules that follows on the same element will cascade on the previous).

Paul D. said on October 10, 2010

I'm pretty sure Safari lead developer Dave Hyatt himself has said that performance issues make a parent selector unfeasible.

yves torres said on October 11, 2010

Totally agree with Arieh, working like this wouldn't be feasible, never mind bloating your html source into a big fast bastard child.

As long as you're not developing the next Yahoo Mail or an application of similar size and scope, I think it's pretty safe to disregard Google Page Speed's results.

Of course there are a few performance tweaks it suggests that do make sense and are easy to implement but other's are just doubtful.

I've got a question about serving gzipped content, is this unproblematic? I've had some bad experiences with caching content, which is a very annoying problem to have.

best
y

Remy Sharp said on October 11, 2010

Whilst I agree with everything you've said - and the performance and rendering demos are great - I don't think a parent selector is a lost cause.

Based on these demos, I can't see why E:parent wouldn't fit perfectly amongst the suite of CSS selectors.

Does it matter that it doesn't follow the "normal" CSS syntax style? I don't think it does. It still cascades, it would still have a specificity weight and I think it's long overdue - as per my post: http://remysharp.com/css-parent-selector/

Scott said on October 11, 2010

Jonathan, thanks for the explanation. You are of course right - once you match that parent you do need to re-evaluate the children.

However, there may be ways to mitigate performance hits - for example combining my and Arieh's suggestions and waiting until certain elements are completely loaded in the DOM before applying styles to their children.

Now when we encounter our .module element, we know the :has selector applies to it. We simply grab its entire HTML contents before applying the CSS. If we find a span along the way, we know to apply those styles. We can think of it like adding a .containsSpan class, in the same way you could think of :nth-child(even) would add .even to alternate elements.

I guess this only works for elements further down the DOM, containing fewer elements. If the selector is applied to the body tag the performance hit is going to return.

Brendan Falkowski said on October 11, 2010

@ Jonathan Snook I can see the application vs. blog angle making a difference to some degree. Rails apps using the domain.com/#/action/name pattern as a single-page controller would naturally ID elements that change the DOM asynchronously. There isn't any extra work to slim the CSS selectors and it helps human readability too.

Factoring JS and DOM manipulation really depends on whether the inactive elements are removed or just hidden. Rendering a new interface (even without a preloaded DOM) would still be much faster than a standard AJAX request I think.

I don't doubt at Yahoo! Mail's DOM complexity the difference is visible. For most sites I'd bet the incremental upgrades in browser engines provide more enhancement than language optimization though. JS has been 1-2x faster with each release.

Jon Raasch said on October 11, 2010

Hey thanks this was a really interesting read, I really like posts that get down to the "why" as opposed to the "how" (or the "do this because I said so").

Kenneth said on October 12, 2010

Thanks, this was nicely explained.

I'm surprised this is blocked on performance grounds though. I can do plenty of CSS that slows my browser down. It's the clever developer that chooses the right tools for the right job. If you're concerned with page speed, it's up to you to re-evaluate your selectors.

It feels a bit like someone's hiding the scissors, so we can't hurt ourselves.

Ian said on October 15, 2010

I don't think we should ever have a parent selector. To cascade means to flow the style down to child elements. If you need to target the parent change the markup not the way CSS works.

Simon said on October 15, 2010

So why do browsers behave like this? Why not apply the CSS after the HTML is fully loaded and use xpath to find the elements? That would be so much faster, easier and powerful. To bad some strange people decided that HTML5 doesn't have to be valid XML, like XHTML and XHTML5

Nicolas Chevallier said on October 17, 2010

Thank you for the article. I just (finally) had managed to understand how the layout elements by browsers. It is much clearer than the explanations given by Steve Souders I find (or that the Google team PageSpeed). And enhanced ability to better manage my CSS rules.

Niels Matthijs said on October 17, 2010

Boo to the Google Page Speed page. While in context that article is not harmful many people will needlessly take over what they are suggesting.

It's okay to worry a little about performance but when it comes to css we have worse problems to tackle. I can't imagine taking over a css of a project I never worked on that was constructed using their guidelines.

Not complaining about this article though. Good reading.

Tyler Herman said on October 19, 2010

I always complained about this (to myself mostly) but I'm glad I at least know the reason now, so I thank you for that.

nyteshade said on October 20, 2010

seems like a :parent pseudo-selector should work like last-element as someone else mentioned before, upon the closing of the tag in question. At this point, go up a single level for #mydiv:parent {color: blue} and target #mydiv's parent element. For multiple parents, #mydiv:parents(p), simply traverse upwards for any p elements that recursively apply to <parent-node>. Parent pathing is almost always a shorter trip then adjacent pathing. In which examples would a parent element not exist?

Montoya said on October 21, 2010

I hate how this wasn't totally obvious already. I just kinda figured people knew this.

Iain Dooley said on October 24, 2010

The one place that I would have liked a parent selector in the past is on hover.

It would be easy enough for a browser to process this on demand rather than during initial page load. So it could be included in CSS but not recommend for use with anything other than :hover states for performance reasons.

Ben Curtis said on October 25, 2010

It's worth keeping in mind that CSS is not a "language" but a standard, and part of the standardization process involves successful implementations of the standard. If the W3C were to declare a parent selector, and all of the browser makers were to say it would cause performance issues, then you would have a few possible outcomes:

  1. The browser makers do not want to implement it, since just the presence of the selector in the CSS would cause the performance hit and any browser that implements it would be thought of as the slowest, least performant browser, and as a result it must be dropped from the standard; or
  2. A few browsers would implement it to be compliant, but would do so in a second pass after all HTML is loaded and would not reevaluate when the DOM changes or on hover events (essentially the same as a JS solution); or
  3. Browsers implement it, take the performance hit, and all sane developers avoid it while other developers use it for simple, inconsequential stylings.

In none of these conditions is the web actually better. In none of these does being "standard compliant" actually improve things. I think that it's vital to remember that we aren't designing a perfect world, but rather paving a road to a better world.

Ben Curtis said on October 25, 2010

I should have re-read my post. I meant in point #1 that if no browsers implement a proposed parent selector because they would lose market share by doing so, then the parent selector would have no implementations and would be stricken from the proposed standard.

Richard said on November 09, 2010

My personal feeling is that the obsession with Page Speed nowadays is a bit pointless.

I remember with dial-ups when speed was probably the priority thing which needed improving, but broadband means that the only reason I get bothered by such a thing is if a site is hosted on a particularly bad server.

I'd rather improvements be made with things like parent selectors, and font embedding to make the page-building side easier.

Stephen Young said on December 02, 2010

@Jonathan Snook Your deep understanding of CSS has forced me to re-evaluate what I thought was my competent understanding of the subject. Great article!

@Iain Dooley: I too wish we had

:hover:parent

Or some other kind of pseudo class for additional hover support. I prefer doing as much mouse-hover-interaction as possible in CSS.

Tom Conte said on December 03, 2010

While we are on the topic of site performance, how do you prefer to write your CSS code? Would you rather keep all styles on one line per selector or list each style on a new line? I assume with very large sites, CSS files can become quite unmanageable and writing code on a new line would be easier to manage. If you throw sub-versioning into the mix, does your opinion change?

Jonathan Snook said on December 04, 2010

@Tom: As you can see by looking at the code for this site, I like the everything on one line. However, working in a larger team, I've had to adjust my style. This was also partially to do with the fact that our heavy use of CSS3 made single-line CSS harder to work with. Now, we do each property/value on its own line. We also break up the files into various components, so each file never really gets that large.

Tom Conte said on December 06, 2010

Thanks for your response Jonathan. Fellow developers of mine, on their own projects, write CSS on one line but working in teams they would switch to multi-line CSS. That seems to be the norm then, though, I haven't heard about CSS3 being an issue with single line CSS.

marc said on May 06, 2011

Why not just render the effects of has() after the element has been completed (all children loaded)?

Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.