3 min read

CSS Selector Performance

Back in July 2014 I had the privilege to talk at the first MelbCSS meetup at 99Designs in front of a respectable crowd of front-end developers from beginners to the very experienced in the community.
At the end of my talk there was a common theme in the questions, almost all came back to the disbelief on how browsers interpret your CSS rules.

A common rule example

Consider

section#layout-rhs .module div {
  /* styles */
}

I put a very similar CSS selector to the audience and asked for someone to guess what is the first interpretation of this rule, specifically, which elements in my DOM would be selected in the dataset of the browsers initial interpretation while parsing this rule.

Here are some of the answers;

  • the child dvi element of element with id of layout-rhs
  • just the element with id of layout-rhs
  • All section elements
  • element with id of layout-rhs but only if it is a section element

Unfortunately I had the expectation that the second or third answer would be correct when i asked this question so i was astonished to get 4 incorrect answers.

My talk was never originally meant to educate on how the browser interprets the CSS selector, as to me that seemed the most basic fundamentals of CSS. I intended to simply engage the audience in my talk with a simple question.

So being confused by the answers I presented a slide with the following phrase.

All CSS rules are read right to left

One person yelled out "no way", while another was giggling. A young woman in the front row had heard this before and as she stated it to the person beside I continued.

What was the first elements selected?

All of the div elements in the entire DOM

That may surprise some of you even today.

Impact

If anyone has spent time looking at performance audits you scoff at me for pointing out a single rule like the one above, and you would be right to say that on its own the difference between that and an #id selector is trivial and measured in thousandths of a second - accumulate all of the CSS selectors of a decent modern app however and we are getting close to that 1 second difference just for not optimising your CSS selectors.

Knowing that CSS selectors are read from RIGHT to LEFT we now consider;

  • The entire DOM is a factor.
  • Complex rules add levels of computation.
  • Unused CSS styles are evaluated.
  • Anything to the left of an ID is irrelevant.
  • Try to remove anything on the left of a good performing selector.
  • Clever rules targeting something specific may not have been very smart after all.
  • Yes, jQuery is also a consideration.

jQuery has it's own CSS selector subset, but it still interprets right to left. Native JavaScript CSS selectors are the significantly faster document.querySelector and document.querySelectorAll which leverages the browser CSS engine directly and therefore is subject to the rules set out for your CSS styles.

Preprocessors

I'm an everyday user of LESS these days, and i maintain one app using SCSS and have ventured into Stylus a few years back.

I am personally pro preprocessors. but they often work against you without you being the wiser.

Preprocessors are great productivity tools.

Consider LESS:

body {
  table {
    &.class1 { 
      tr td {
        /* cell styles */
      }
    } 
  }
  .class2 {
    /* class styles */
    &.active {
      /* conditional styles */
    }
  }
} 

Compiled:

body { }
body table.class1 { }
body table.class1 tr { }
body table.class1 tr td { }
body .class2 { }
body .class2.active { }

Preprocessors can offer great structure, and maintainability. But what is wrong here in terms of performance?

  • Overly complex rules for the browser interpretation
  • Multiple levels of computation due to the encapsulated classes
  • Has simple vanilla CSS implementation

Order of efficiency

Selectors efficiency may not suite all use cases. Don't eliminate using pseudo selector in fear just optimise their implementation.

  • ID - example: #unique
  • Class - example: .reusable
  • Tags - example: tr
  • Adjacent sibling - example: tr + th
  • Child - example: div > p
  • Descendant - example: div a
  • Attribute - example: [type="email"]
  • Pseudo - example: :focus

Please don't go crazy and style ID's everywhere

Naming Conventions

The awesome front-end developer Hannah Thompson at Punters.com.au whom I work with was responcible for implementing BEM to our already very mature code base so that we could focus more on our components without the confusion of CSS and concern about performance.

I encourage you to use BEM, it will change the way you look back at CSS when debugging when you realise how simple it has become.

Conclusion

We've learned that browser read from right to left, and that nesting your preprocessor css is generally not a good idea so keep it 2 levels deep at most.

ID selectors are best, and close behind class selectors are your optimal choice and work in all naming conventions.

Enjoyed this?

If you made it here I would love to hear what you thought of the article.