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. 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.