Guide
Calculating CSS specificity, including :is(), :not() and :where()
When two CSS rules set the same property on the same element, the browser has to pick one. After origin, importance and cascade layers, the deciding factor is specificity: a score computed from the selector. Most “why is my style not applying?” bugs come down to it. This calculator scores selectors the way browsers do, shows which parts count and tells you which selector wins.
The three-part score
Specificity is written as three numbers, (a, b, c). The first counts ID selectors such as #nav. The second counts class selectors such as .item, attribute selectors such as [type="text"] and pseudo-classes such as :hover or :first-child. The third counts type selectors such as a or li and pseudo-elements such as ::before. The universal selector * and combinators such as >, +, ~ and the space count nothing.
Comparing scores
Scores are compared column by column, not added up. The selector with more IDs wins outright; only if the IDs are equal do the classes matter, and only then the types. One ID, (1,0,0), therefore beats ninety-nine classes, (0,99,0). When two selectors have exactly the same score, the rule that appears later in the stylesheet wins, which is why the calculator reports ties by source order.
The functional pseudo-classes
Selectors Level 4 added pseudo-classes that take selector lists, each with its own rule. :is(), :not() and :has() count as their most specific argument, so a:not(.external, #home) scores (1,0,1) because of the ID inside. :where() always counts zero, which makes it ideal for base styles that should be easy to override. :nth-child(2 of .done) counts one pseudo-class plus the most specific selector after of.
Where specificity sits in the cascade
Specificity is not the first test. The cascade first compares origin and importance, so an !important declaration beats a normal one whatever the selectors, and user-agent, user and author styles have their own order. Cascade layers come next: rules in a later layer win over earlier layers. Inline style attributes beat any selector. Only after all that does specificity decide, followed by source order.
A worked example
The Sample lists eight selectors. #nav .item > a:hover scores (1,2,1), nav ul li a (0,0,4) and .btn.btn-primary (0,2,0). a:not(.external, #home) scores (1,0,1) because of the ID in its argument, while :where(.card) h2 is only (0,0,1). :is(header, footer) a[href^="https"] scores (0,1,2), li:nth-child(2 of .done) (0,2,1) and button::after (0,0,2). The winner is #nav .item > a:hover: it ties with the :not() selector on IDs and wins on classes.
Getting out of specificity wars
Rising specificity, IDs in stylesheets, chains of classes and !important, makes CSS hard to change. Keep selectors short and class-based, wrap reusable base styles in :where() so components can override them with a single class, and use cascade layers to order frameworks, components and utilities explicitly instead of fighting with selector weight.
Debugging a style that will not apply
Open the element in the browser’s developer tools and look at the Styles panel: overridden declarations are struck through, and the rule above them is the one that won. Paste both selectors here to see the scores side by side. If the winning rule has a higher score, either raise the losing selector’s specificity by one class or, better, lower the winner’s, for example by replacing an ID with a class. If the scores are equal, the problem is order: the winning rule is simply loaded later, often from another stylesheet or a framework.
Related tools
- CSS to XPath converts the same selectors for test frameworks.
- CSS to Tailwind replaces specificity-heavy rules with utility classes.
- Code Minifier compresses the final stylesheet.
- HTML Accessibility Checker reviews the markup the selectors target.
Checked against the current version of the CSS Specificity Calculator on .

