Scouter: The CSS Selector Specificity Calculator
When you're developing a tool like Divshot there's no shortage of fun and interesting challenges. One such challenge we ran into was prioritizing components with similar selectors in our component engine. Two components could target the same element but we wanted to select the component with the most specific selector between the two. Here's a simple example:
Component #1 is a title bar button with options designated specifically for buttons inside title bars:
.title-bar > .button
Component #2 is a button for use anywhere:
So when I click a button inside a title bar it should default to Component 1. Otherwise, default to Component 2. A simple solution would be to determine the specificity of each selector string. I created Scouter for a quick and easy way to get the specificity score of a selector. Like many of our smaller utility projects it's open source. It also relies on regular expressions, something that I'd love to have scrutinized and improved by an unsung regex hero on GitHub.
A selector's specificity is calculated as follows:
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
Selectors inside the negation pseudo- class are counted like any other, but the negation itself does not count as a pseudo-class.
Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.
The specification is very straightforward, only requiring 40 lines of CoffeeS cript. Let's walk through the code and figure out how it works:
First things first we split up the selector by descendant, sibling, and child combinators (" ", >, +, ~).
Next we pick up all the matches we need: IDs, classes, attributes, types, pseudo-classes, and pseudo-elements. However, we omit the :not() pseudo-class since it doesn't count toward the specificity score.
That's our parse method in a nutshell. Parse returns an array of matches and now we need to figure out which match belongs to which number: A, B, or C.
We filter the matches by checking for "#" and throw the IDs inside A. Classes, attributes, and pseudo-classes go inside B. Afterwards we filter out everything in A and B from the matches array to get the remaining matches. Those remaining most likely belong in C.
That's it. The final score is tallied and you have your specificity score.
Check it out on GitHub. If you have any questions leave a comment. Enjoy!