Paste an HTML fragment and a selector. Matching uses your browser’s selector support; the specificity summary is not a full cascade analysis.
Runs locally in your browserPaste an HTML fragment and a CSS selector, then press Test selector. The fragment is parsed into a detached document that is never attached to the page, and the selector runs through the browser's own querySelectorAll, so a selector that matches here is a selector your browser accepts.
The summary shows the specificity of the most specific group, how many elements matched, and how many comma-separated groups the selector contains. Each match is listed as tag#id.class with the first 150 characters of its text.
a counts ID selectors, b counts classes, attributes and pseudo-classes, and c counts type selectors and pseudo-elements. The universal selector counts zero: * is 0,0,0, .card > a#download is 1,1,1, ul li::before is 0,0,3 and [href] is 0,1,0.
With a comma-separated selector the tile shows the most specific group, which is the number the browser applies to the declarations written inside that group: a, #b reads 1,0,0. Nothing here weighs !important, @layer order or inheritance, so the tiles are a specificity count, not a cascade simulation.
:not(), :is() and :has() score as their most specific argument: :is(#a, .b) is 1,0,0, :not(.a) is 0,1,0 and :where(#a) is 0,0,0. :nth-child(2 of .a) adds the list that follows of, which makes it 0,2,0.
An escaped character stays inside its identifier, so .a\.b is one class (0,1,0), and a namespace prefix is not a type selector: svg|circle counts one type, not two.
The fragment is parsed without a stylesheet, so nothing is laid out and no element is in a state: p:hover, p:visited and p:focus match nothing here. Scripts inside the fragment do not run and images are not fetched.
Matching follows your browser. Chrome rejects svg|circle and [href i], and the status line then prints that message verbatim instead of a result. Long fragments are still matched in full: a 400-row table produced 400 cards.