惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
GbyAI
GbyAI
博客园 - Franky
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
InfoQ
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
博客园 - 【当耐特】
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Project Zero
Project Zero
量子位
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Blog of Author Tim Ferriss
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
S
SegmentFault 最新的问题
L
LINUX DO - 最新话题
Simon Willison's Weblog
Simon Willison's Weblog
爱范儿
爱范儿
博客园 - 聂微东
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
D
Docker

CSS Wizardry

Front-End’s Missing Metric: The TBT Window Meet Your Users Where They Are with Obs.js Better Browser Caching with No-Vary-Search font-family Doesn’t Fall Back the Way You Think What Is CSS Containment and How Can I Use It? When All You Can Do Is All or Nothing, Do Nothing Obs.js: Context-Aware Web Performance for Everyone Low- and Mid-Tier Mobile for the Real World (2025) The Fastest Site in the Tour de France Making Sense of the Performance Extensibility API Why Do We Have a Cache-Control Request Header? HTML Is Not a Programming Language… Build for the Web, Build on the Web, Build with the Web Licensing Code on CSS Wizardry A Layered Approach to Speculation Rules Designing (and Evolving) a New Web Performance Score Core Web Vitals Colours The Ultimate Contract Templates for Tech Consultants: Protect Your Business and Get Paid Optimising for High Latency Environments Cache Grab: How Much Are You Leaving on the Table? blocking=render: Why would you do that?! Correctly Configure (Pre) Connections The Three Cs: 🤝 Concatenate, 🗜️ Compress, 🗳️ Cache What Is the Maximum max-age? How to Clear Cache and Cookies on a Customer’s Device The Ultimate Low-Quality Image Placeholder Technique Core Web Vitals for Search Engine Optimisation: What Do We Need to Know? The HTTP/1-liness of HTTP/2 In Defence of DOM­Content­Loaded Site-Speed Topography Remapped Why Not document.write()? Speeding Up Async Snippets Critical CSS? Not So Fast! Measure What You Impact, Not What You Influence Optimising Largest Contentful Paint Measuring Web Performance in Mobile Safari Site-Speed Topography Speed Up Google Fonts Real-World Effectiveness of Brotli Performance Budgets, Pragmatically Lazy Pre-Browsing with Prefetch Making Cloud.typography Fast(er) Time to First Byte: What It Is and How to Improve It Self-Host Your Static Assets Tips for Technical Interviews Cache-Control for Civilians Bandwidth or Latency: When to Optimise for Which ITCSS × Skillshare What If? CSS and Network Performance The Three Types of Performance Testing Getting to Know a Legacy Codebase Image Inconsistencies: How and When Browsers Download Images Identifying, Auditing, and Discussing Third Parties My Digital Music Setup Measuring the Hard-to-Measure Finding Dead CSS The Fallacies of Distributed Computing (Applied to Front-End Performance) Ten Years Old Relative Requirements Airplanes and Ashtrays Performance and Resilience: Stress-Testing Third Parties Refactoring Tunnels Little Things I Like to Do with Git Writing Tidy Code Configuring Git and Vim Base64 Encoding & Performance, Part 2: Gathering Data Base64 Encoding & Performance, Part 1: What’s Up with Base64? Code Smells in CSS Revisited Typography for Developers Moving CSS Wizardry onto HTTPS and HTTP/2 Ack for CSS Developers A New Year, a New Focus Preparing Vim for Apple’s Touch Bar Choosing the Correct Average CSS Shorthand Syntax Considered an Anti-Pattern CSS Wizardry Newsletter Nesting Your BEM? Improving Perceived Performance with Multiple Background Images Continue Normalising Your CSS Pure CSS Content Filter Pragmatic, Practical, and Progressive Theming with Custom Properties Refactoring CSS: The Three I’s Speaker’s Checklist: Before and After Your Talk Improving Your CSS with Parker The Importance of !important: Forcing Immutability in CSS Mixins Better for Performance Managing Typography on Large Apps White October Events Workshop Partnership BEMIT: Taking the BEM Naming Convention a Step Further Travelling Like You Want to, When You Have To Contextual Styling: UI Components, Nesting, and Implementation Detail Subtleties with Self-Chained Classes Cyclomatic Complexity: Logic in CSS Immutable CSS Can CSS Be Too Modular? More Transparent UI Code with Namespaces When to use @extend; when to use a mixin The Specificity Graph CSS Wizardry Ltd.: Year 1 in review
Writing efficient CSS selectors
Harry Roberts · 2011-09-17 · via CSS Wizardry

Written by on CSS Wizardry.

Table of Contents

Independent writing is brought to you via my wonderful Supporters.

  1. CSS selectors
    1. Combining selectors
  2. The key selector
  3. Overqualifying selectors
  4. Is all this really necessary?
    1. But
    2. More on CSS selector efficiency

Efficient CSS is not a new topic, nor one that I really need to cover, but it’s something I’m really interested in and have been keeping an eye on more and more since working at Sky.

A lot of people forget, or simply don’t realise, that CSS can be both performant and non-performant. This can be easily forgiven however when you realise just how little you can, err, realise, non-performant CSS.

These rules only really apply to high performance websites where speed is a feature, and 1000s of DOM elements can appear on any given page. But best practice is best practice, and it doesn’t matter whether you’re building the next Facebook, or a site for the local decorator, it’s always good to know…

CSS selectors

CSS selectors will not be new to most of us, the more basic selectors are type (e.g. div), ID (e.g. #header) and class (e.g. .tweet) respectively.

More uncommon ones include basic pseudo-classes (e.g. :hover) and more complex CSS3 and ‘regex’ selectors, such as :first-child or [class^="grid-"].

Selectors have an inherent efficiency, and to quote Steve Souders, the order of more to less efficient CSS selectors goes thus:

  1. ID, e.g. #header
  2. Class, e.g. .promo
  3. Type, e.g. div
  4. Adjacent sibling, e.g. h2 + p
  5. Child, e.g. li > ul
  6. Descendant, e.g. ul a
  7. Universal, i.e. *
  8. Attribute, e.g. [type="text"]
  9. Pseudo-classes/-elements, e.g. a:hover

Quoted from Even Faster Websites by Steve Souders

It is important to note that, although an ID is technically faster and more performant, it is barely so. Using Steve Souders’ CSS Test Creator we can see that an ID selector and a class selector show very little difference in reflow speed.

In Firefox 6 on a Windows machine I get an average reflow figure of 10.9 for a simple class selector. An ID selector gave a mean of 12.5, so this actually reflowed slower than a class.

The difference in speed between an ID and a class is almost totally irrelevant.

A test selecting on a type (<a>), rather than a class or ID, gave a much slower reflow.

A test on a heavily overqualified descendant selector gave a figure of around 440!

From this we can see that the difference between IDs/classes and types/descendants is fairly huge… The difference between themselves is slight.

N.B. These numbers can vary massively between machine and browser. I strongly encourage you to run/play with your own.

Combining selectors

You can have standalone selectors such as #nav, which will select any element with an ID of ‘nav’, or you can have combined selectors such as #nav a, which will match any anchors within any element with an ID of ‘nav’.

Now, we read these left-to-right. We see that we’re looking out for #nav and then any a elements inside there. Browsers read these differently; browsers read selectors right-to-left.

Where we see a #nav with an a in it, browsers see an a in a #nav. This subtle difference has a huge impact on selector performance, and is a very valuable thing to learn.

For an in-depth reason as to why they do this see this discussion on Stack Overflow.

It’s more efficient for a browser to start at the right-most element (the one it knows it wants to style) and work its way back up the DOM tree than it is to start high up the DOM tree and take a journey down that might not even end up at the right-most selector–also known as the key selector.

This has a very significant impact on the performance of CSS selectors…

The key selector

The key selector, as discussed, is the right-most part of a larger CSS selector. This is what the browser looks for first.

Remember back up there we discussed which types of selector are the most performant? Well whichever one of those is the key selector will affect the selector’s performance; when writing efficient CSS it is this key selector that holds the, well, key, to performant matching.

A key selector like this:

#content .intro {}

Is probably quite performant as classes are an inherently performant selector. The browser will look for all instances of .intro (of which there aren’t likely to be many) and then go looking up the DOM tree to see if the matched key selector lives in an element with an ID of ‘content’.

However, the following selector is not very performant at all:

#content * {}

What this does is looks at every single element on the page (that’s every single one) and then looks to see if any of those live in the #content parent. This is a very un-performant selector as the key selector is a very expensive one.

Using this knowledge we can make better decisions as to our classing and selecting of elements.

Let’s say you have a massive page, it’s enormous and you’re a big, big site. On that page are hundreds or even thousands of <a>s. There is also a small section of social media links in a <ul> with an ID #social; let’s say there is a Twitter, a Facebook, a Dribbble and a Google+ link. We have four social media links on this page and hundreds of other anchors besides.

This selector therefore is unreasonably expensive and not very performant:

What will happen here is the browser will assess all the thousands of links on that page before settling on the four inside of the #social section. Our key selector matches far too many other elements that we aren’t interested in.

To remedy this we can add a more specific and explicit selector of .social-link to each of the <a>s in the social area. But this goes against what we know; we know not to put unnecessary classes on elements when we can use (c)leaner markup.

This is why I find performance so interesting; it’s a weird balance between web standards best practices and sheer speed.

Whereas we would normally have:

<ul id="social">
    <li><a href="#" class="twitter">Twitter</a></li>
    <li><a href="#" class="facebook">Facebook</a></li>
    <li><a href="#" class="dribble">Dribbble</a></li>
    <li><a href="#" class="gplus">Google+</a></li>
</ul>

with this CSS:

We’d now have:

<ul id="social">
    <li><a href="#" class="social-link twitter">Twitter</a></li>
    <li><a href="#" class="social-link facebook">Facebook</a></li>
    <li><a href="#" class="social-link dribble">Dribbble</a></li>
    <li><a href="#" class="social-link gplus">Google+</a></li>
</ul>

with this CSS:

This new key selector will match far fewer elements and means that the browser can find them and style them faster and can move on to the next thing.

And, we can actually get this selector down further to .social-link {} by not overqualifying it; read on to the next section for that…

So, to recap, your key selector is the one which determines just how much work the browser will have to do, so this is the one to keep an eye on.

Overqualifying selectors

Okay so now we know what a key selector is, and that that is where most of the work comes from, we can look to optimise further. The best thing about having nice explicit key selectors is that you can often avoid overqualifying selectors. An overqualified selector might look like:

html body .wrapper #content a {}

There is just too much going on here, and at least three of these selectors are totally unnecessary. That could, at the very most, be this:

So what?

Well the first one means that the browser has to look for all a elements, then check that they’re in an element with an ID of ‘content’, then so on and so on right the way up to the html. This is causing the browser way too many checks that we really don’t need. Knowing this, we can get more realistic examples like this:

Down to just:

We know that if the a is inside an li it has to be inside the #nav so we can instantly drop the li from selector. Then, as the nav has an ID we know that only one exists in the page, so the element it is applied to is wholly irrelevant; we can also drop the ul.

Overqualified selectors make the browser work harder than it needs to and uses up its time; make your selectors leaner and more performant by cutting the unnecessary bits out.

Is all this really necessary?

The short answer is; probably not.

The longer answer is; it depends on the site you’re building. If you’re working on your next portfolio then go for clean code over CSS selector performance, because you really aren’t likely to notice it.

If you’re building the next Amazon, where microseconds in page speeds do make a difference then maybe, but even then maybe not.

Browsers will only ever get better at CSS parsing speeds, even mobile ones. You are very unlikely to ever notice slow CSS selectors on a websites but

But

It is still happening, browsers still are having to do all the work we’ve talked about, no matter how quick they get. Even if you don’t need or even want to implement any of this it is something that is definitely worth knowing. Bear in mind that selectors can be expensive and that you should avoid the more glaring ones where possible. That means if you find yourself writing something like:

div:nth-of-type(3) ul:last-child li:nth-of-type(odd) * { font-weight:bold }

Then you’re probably doing it wrong.

Now, I’m still kind of new to the world of selector efficiency myself so if I’ve missed anything, or you have anything to add, please pop it in the comments!

More on CSS selector efficiency

I cannot recommend the website and books of Steve Souders enough. That’s pretty much all the further reading recommendation you’ll need. The guy knows his stuff!