Calculate the Specificity of a CSS Selector with @bramus/specificity

Update 2022.03.31 The code examples included on this page is outdated as @bramus/specificity 2.0.0 has been released. Read the updated documentation for that version here.

To calculate the Specificity of a CSS Selector there are several rules to follow. To not have to manually calculate the specificity, you can resort to online tools such as the wonderful CSS Specificity Calculator by Kilian Valkhof.

When it comes to calculating specificity from within your own JavasScript code, there’s no package with proper Selectors Level 4 support to use … until now!

~

# The Package

Last night, sparked by Kilian’s online tool, I created a package named @bramus/specificity. It comes as an ES Module and exposes a calculate function to calculate the specificity of a given CSS selector.

Installation per NPM:

npm i @bramus/specificity

Once installed, you can use it as follows:

import { calculate } from '@bramus/specificity';

const specificity = calculate('.foo :is(.bar, #baz)');
// ~> { a: 1, b: 1, c: 0 }

A CJS-compatible build is also included.

~

# Implementation Demo

Here’s a quick demo that consumes the package:

See the Pen
Calculate Specificity
by Bramus (@bramus)
on CodePen.

Note that this is a really simple implementation. It only shows the specificity, it doesn’t explain why it is calculated like that. If you’re looking for something fancier / more useful, go check out the CSS Specificity Calculator by Kilian Valkhof / Polypane

~

# The Return Format

Contrary to popular belief, CSS Specificity is not just a single number. It’s a triad, printed as (A,B,C). Therefore an Object with the keys a, b, c is returned.

In a future release, the return format might be extended to contain more info. I’m open to input on this.

~

# Under the hood

The heavy lifting is done by CSSTree, a tool that can parse CSS to an AST.

const ast = csstree.parse(selector, {
     context: 'selectorList',
});

By manually walking the generated AST, the Specificity is calculated by following the Specificity Rules:

  • Count the number of ID selectors in the selector (= A)
  • Count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= B)
  • Count the number of type selectors and pseudo-elements in the selector (= C)
  • Ignore the universal selector

Unlike other libraries, @bramus/specificity also plays nice with “evaluation contexts” that have their specificity defined specially:

  • The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the specificity of the most specific complex selector in its selector list argument.
  • Analogously, the specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument (if any).
  • The specificity of a :where() pseudo-class is replaced by zero.

~

# Limitations

As mentioned above, the return format currently is nothing but a simple Object which you’ll have to process yourself. In the future, the format might get extended or some extra methods may be exposed.

Furthermore input is now locked to a single Selector, not a SelectorList. This might change in an upcoming release.

~

# Links

@bramus/specificity Source (GitHub) →
@bramus/specificity on NPM →

~

# Spread the word

To help spread the contents of this post, feel free to retweet the announcement tweet:

~

🔥 Like what you see? Want to stay in the loop? Here's how:

Published by Bramus!

Bramus is a frontend web developer from Belgium. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.