How to test a CSS selector against an HTML fragment

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

  1. Paste the markup you want to query, or press Load example for a snippet with main.app, two article.card elements and an a#download link.
  2. Type the selector in the CSS selector field and press Test selector. The field starts with .card > a#download.
  3. Read the three tiles: specificity as a,b,c, the number of matches, and the number of selector groups.
  4. Check the cards. .card > a#download matches one element, a#download.button.primary, and its text, Download, is printed under the name.
  5. Press Clear to empty both fields and reset the panes.

Reading the specificity breakdown

What each number of a,b,c counts

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.

The cases that are easy to get wrong

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

What this tester cannot tell you

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.

Recent tools: