What’s new in @bramus/specificity v2

Back in February I created @bramus/specificity, an NPM package to calculate the Specificity of CSS Selectors.

As that version was more of a thought experiment/POC, there was a lot of room for improvement. Yesterday, after 11 betas, version 2.0.0 of @bramus/specificity was released. Let’s take a look …


Quick Example

To give you an idea of what it’s all about, here’s a quick demo:

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

The input accepts a string that contains one or more CSS Selector(s) — a Selector List. @bramus/specificity will calculate the specificity for each Selector that it detects (powered by csstree).


Notable Changes since v1

✨ Introduce and use a Specificity class

Where v1 exposed a standalone calculate function which returned simple Objects, v2 now exposes a Specificity class which represents a calculated specificity. The calculate function is now a static method of that class.

import Specificity from '@bramus/specificity';

const selectors = 'header:where(#top) nav li:nth-child(2n), #doormat';
const specificities = Specificity.calculate(selectors);

specificities.map((s) => s.toString());
// ~> ["(0,1,3)", "(1,0,0)"]

The class also has many instance methods for you to use.

  • Read the specificity value using one of its accessors:

    const s = specificities[0];
    s.value; // { a: 0, b: 1, c: 3 }
    s.a; // 0
    s.b; // 1
    s.c; // 3
  • Convert the calculated value to various formats using one of the toXXX() instance methods:

    s.toString(); // "(0,1,3)"
    s.toArray(); // [0, 1, 3]
    s.toObject(); // { a: 0, b: 1, c: 3 }
  • Extract the matched selector string:

    s.selectorString(); // "header:where(#top) nav li:nth-child(2n)"
  • Use one of its instance comparison methods to compare it to another Specificity instance:

    s.isEqualTo(specificities[1]); // false
    s.isGreaterThan(specificities[1]); // false
    s.isLessThan(specificities[1]); // true
  • Don’t worry about using JSON.stringify():

    // {
    //    "selector": 'header:where(#top) nav li:nth-child(2n)',
    //    "asObject": { "a": 0, "b": 1, "c": 3 },
    //    "asArray": [0, 1, 3],
    //    "asString": "(0,1,3)",
    // }


👨‍👩‍👧‍👦 Support Selector Lists

v1 only accepted single selectors to calculate. v2 accepts Selector Lists. Because of that, Specificity.calculate(…) will always return an array, with each entry being a Specificity instance — one per found selector.

If you know you’re passing only a single Selector into Specificity.calculate(), you can use JavaScript’s built-in destructuring to keep your variable names clean.

const [s] = Specificity.calculate('header:where(#top) nav li:nth-child(2n)');
s.value; // { a: 0, b: 1, c: 3 }


🗜 Reduced Bundle Size

By only importing the selector-parser from css-tree, the bundle size was greatly reduced. Thanks to a code contribution to css-tree, some of the code in @bramus/specificity could also be removed.


🔀 Utility functions for comparing, sorting, and filtering

On the Specificity class, several static methods are exposed for comparing, sorting, and filtering.

  • Comparing:

    • Specificity.compare(s1, s2): Compares s1 to s2. Returns a value that can be:
      • > 0 = Sort s2 before s1 (i.e. s1 is more specific than s2)
      • 0 = Keep original order of s1 and s2 (i.e. s1 and s2 are equally specific)
      • < 0 = Sort s1 before s2 (i.e. s1 is less specific than s2)
    • Specificity.equals(s1, s2): Returns true if s1 and s2 have the same specificity. If not, false is returned.
    • Specificity.greaterThan(s1, s2): Returns true if s1 has a higher specificity than s2. If not, false is returned.
    • Specificity.lessThan(s1, s2): Returns true if s1 has a lower specificity than s2. If not, false is returned.
  • Sorting:

    • Specificity.sortAsc(s1, s2, …, sN): Sorts the given specificities in ascending order (low specificity to high specificity)
    • Specificity.sortDesc(s1, s2, …, sN): Sorts the given specificities in descending order (high specificity to low specificity)
  • Filtering:

    • Specificity.min(s1, s2, …, sN): Filters out the value with the lowest specificity
    • Specificity.max(s1, s2, …, sN): Filters out the value with the highest specificity

A specificity passed into any of these utility functions can be any of:

  • An instance of the included Specificity class
  • A simple Object such as {'a': 1, 'b': 0, 'c': 2}

These helper functions can also be imported as standalone functions, thanks to the use of SubPath Exports.

import { compare, equals, greaterThan, lessThan } from '@bramus/specificity/compare';
import { min, max } from '@bramus/specificity/filter';
import { sortAsc, sortDesc } from '@bramus/specificity/sort';


🤖 Type Definitions

Although @bramus/specificity is written in Vanilla JavaScript, it does include Type Definitions which are exposed via its package.json.


💻 CLI script

@bramus/specificity exposes a binary named specificity to calculate the specificity of a given selector list on the CLI. For each selector that it finds, it’ll print out the calculated Specificity as a string on a new line.

$ specificity "header:where(#top) nav li:nth-child(2n), #doormat"


Getting @bramus/specificity

@bramus/specificity’s source is available on GitHub and is distributed through NPM:

npm i @bramus/specificity

If you encounter any issues, you can leave them in the Issue Tracker.


Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small donation would surely put a smile on my face. Thanks!

Sponsor on GitHub

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. 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 …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

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