CSS Specificity Calculator
runs in your browserScore CSS selectors as IDs, classes and types, see which is most specific, and paste a whole stylesheet: nested rules count & as the rule they sit in.
Selectors one per line or between commas, or a whole stylesheet: rules nested inside another are counted with & as that rule.
Selectors
Each selector is scored as three counts: IDs; classes, attributes and pseudo-classes; types and pseudo-elements.
Specificity
The three boxes are IDs · classes, attributes and pseudo-classes · types and pseudo-elements, compared left to right.
Scores appear here as you type.
about this tool
Three counts, compared left to right
When two rules set the same property on the same element, the browser keeps
the one whose selector is more specific, unless importance, a style
attribute or a cascade layer has already decided between them. Specificity is three numbers: the IDs
in the selector; its classes, attribute selectors and pseudo-classes; and its
type selectors and pseudo-elements. They are compared like the digits of a
number in an unlimited base, so the first column decides unless it is equal:
#nav a (1,0,1) beats .nav .menu li a (0,2,2), and no number of classes
outweighs one ID. The universal selector * and the combinators add nothing.
Each selector you paste is shown with the three counts and the parts that
made them, so a score you did not expect can be traced to the piece that
caused it. Switch the order to most specific first to see them ranked, and
the line above the list says which would win on specificity and order if they
all matched the same element, and on which column. It does not weigh
@layer, !important or inline styles; when a pasted stylesheet has layers
or !important, the tool says so.
The newer selectors
The rules follow Selectors Level 4:
:is(),:not()and:has()count as their most specific argument, regardless of which argument matched::is(#main, .content) pis 1,0,1 everywhere it applies.:where()counts as nothing, whatever is inside it.:nth-child(An+B of S)and:nth-last-child()count as one pseudo-class plus their most specificofselector.::before,::markerand the other pseudo-elements count with the types, including the four that CSS 2 wrote with one colon (:before,:after,:first-line,:first-letter).
::slotted() and :host() add their argument to their own count, as
CSS Scoping defines, and a
view-transition pseudo-element named * counts as nothing.
Nested CSS
Paste a whole stylesheet and its rules are found inside @media,
@supports, @layer, @container and @scope, with declarations and the
blocks of @font-face and @keyframes passed over. In a rule nested inside
another, & counts as the most specific selector of the rule around it, as
CSS Nesting defines it.
A nested selector with no & anywhere in it is read as if it started with
& , so in .card { .title { } } the inner rule is 0,2,0. With a parent
list such as #a, .b, & counts as #a even for an element that matched
only .b. & cannot stand for a pseudo-element, so a rule nested in
.a::before matches nothing, and the tool says so.
Typed on its own, without the rule around it, & cannot be counted; the
tool scores the rest and says the result is a floor. Directly inside
@scope, & behaves as :where(:scope), as CSS Cascade 6 defines it, so it
matches the scoping root and adds nothing.
Sass is not CSS: &__elem and &-suffix glue text onto the parent's name,
which CSS nesting does not do, so a browser drops them, and $variables,
@mixin and // comments mean nothing to it. The tool says so when it sees
them; compile Sass to CSS first.
What it checks, and what it does not
A selector the browser would reject is shown as invalid, with the reason: an
ID or class starting with a digit (it needs an escape, such as #\31 23), a
combinator with nothing after it, two combinators in a row, a class or a
combinator after a pseudo-element, an An+B that is not one, an unclosed bracket. :is() and
:where() skip an argument they cannot read and keep the rest; :not() and
:has() reject the whole selector. In a stylesheet, one bad selector in a
rule's list drops the whole rule, so its other selectors and the rules
nested in it are marked as dropped too.
One case is allowed by the spec but not by browsers: a user-action
pseudo-class after a pseudo-element, as in a::after:hover. Browsers drop
the rule, so the tool marks it invalid and suggests a:hover::after, which
styles the ::after while the link is hovered.
Pseudo-classes and pseudo-elements are checked against the standard names. One the tool does not know still counts, with a note: a browser drops a selector whose pseudo-class it does not support, and which ones it supports varies by browser and version.
Specificity decides only between declarations of equal standing. Before it
is compared, !important declarations beat normal ones; a declaration in a
style attribute beats any selector of the same importance; and for normal
declarations an unlayered rule beats a layered one (for !important ones
the order of layers reverses). After specificity, a rule inside @scope
beats an equally specific one outside any @scope, or one whose scope root
is further from the element, whatever their order; only then does the later
rule win. None of that is in a selector, so none of it is scored here. Up to
2,000 selectors are scored at once, and the tool says how many more it left
out. Nothing you paste is stored or sent anywhere.
For tidying the stylesheet itself, the CSS formatter lays it out and minifies it.
questions
- How is CSS specificity calculated?
- As three counts, compared left to right: the IDs in the selector; then its classes, attribute selectors and pseudo-classes; then its type selectors and pseudo-elements. The universal selector * and the combinators (space, >, + and ~) add nothing. So #nav a scores 1,0,1 and beats .nav .menu li a at 0,2,2, because the first column decides before the others are looked at. A thousand classes never outweigh one ID.
- What is the specificity of :is(), :not(), :has() and :where()?
- :is(), :not() and :has() take the specificity of their most specific argument, regardless of which argument matched: :is(#main, .content) p scores 1,0,1 even on an element that matched only .content. :where() always scores 0,0,0, which is why it is used to write defaults that anything can override. :nth-child(2n of .active) counts as one pseudo-class plus its most specific “of” selector.
- How does & affect specificity in nested CSS?
- The nesting selector & counts as the most specific selector of the rule it is nested in, like :is() around that rule’s selector list. A nested selector with no & is read as if it began with “& ”, so .card { .title { } } scores 0,2,0, not 0,1,0. With a parent list such as #a, .b, & counts as #a for every element, even one that matched only .b — easy to miss when moving from Sass, which copies the parent selector instead.
- What happens when two selectors have the same specificity?
- The one that comes later wins — later in the same stylesheet, or in a later one — provided nothing earlier in the cascade has decided it. Before specificity is compared, !important declarations beat normal ones; a style attribute beats any selector of the same importance; and for normal declarations an unlayered rule beats a layered one, with the order of layers reversed for !important. And when two rules tie on specificity, one inside @scope beats one outside any @scope, or one whose scope root is further from the element, before source order is used.
- Is anything I paste sent anywhere?
- No. The selectors are read and scored in your browser as you type; nothing is uploaded or stored, and closing the tab discards it.