WW Tools
All references

CSS Selectors Reference

The full set of selectors a browser's querySelectorAll accepts, with the attribute operators written out and the specificity contribution of each construct. Pseudo-classes that depend on a rendered page are listed separately, because they are the usual reason a selector that looks correct matches nothing in parsed markup.

Simple selectors

SelectorNameMatchesExample
*universalEvery element.*
typetypeElements with that tag name.p
.classclassElements whose class list contains that name..card
#ididThe element with that id.#main
[attr]attribute presenceElements that have the attribute at all, whatever its value.[disabled]
[attr=v]exactAttribute value equal to v.[type="text"]
[attr~=v]whitespace listValue is a space-separated list containing v as a whole word.[rel~="noopener"]
[attr|=v]hyphen prefixValue is v, or starts with v followed by a hyphen.[lang|="en"]
[attr^=v]prefixValue starts with v.[href^="https://"]
[attr$=v]suffixValue ends with v.[src$=".svg"]
[attr*=v]substringValue contains v anywhere.[class*="col-"]
[attr=v i]case flagSame as the operator before it, compared case-insensitively. The s flag forces case-sensitivity.[type="TEXT" i]

Combinators

CombinatorNameMeaningExample
spacedescendantB anywhere inside A, at any depth.article p
>childB as a direct child of A.ul > li
+next siblingB immediately after A, same parent.h2 + p
~subsequent siblingEvery B after A, same parent.h2 ~ p

Structural pseudo-classes

Pseudo-classMatchesNote
:first-childAn element that is the first child of its parent.Counts element siblings only. Text nodes are ignored.
:last-childThe last child of its parent.
:only-childAn element with no element siblings.
:nth-child()Positional match on An+B, odd, even, or a literal integer.Counts all element siblings, whatever their type.
:nth-last-child()The same, counting from the end.
:first-of-typeFirst sibling with that tag name.Counts only siblings of the same type.
:last-of-typeLast sibling with that tag name.
:nth-of-type()Positional match among siblings of the same tag name.
:only-of-typeThe only sibling with that tag name.
:emptyAn element with no children at all, text nodes included.A single space between the tags defeats it. Comments do not.
:rootThe document root, which is html in an HTML document.
:not()Elements not matching the argument.Not forgiving: an invalid argument invalidates the whole selector.
:is()Elements matching any branch of the argument list.Forgiving: an invalid branch is dropped silently and the rest still match.
:where()The same as :is(), with zero specificity.Forgiving in the same way.
:has()Elements containing something matching the argument.Forgiving. Cannot be nested inside another :has().

Pseudo-classes that can never match parsed markup

Pseudo-classWhy
:hoverNeeds a live pointer position.
:focusNeeds a focused element, which requires a rendered document.
:focus-visibleNeeds focus plus the browser's heuristic about how it was given.
:activeNeeds a pointer or key held down right now.
:visitedNeeds browsing history, and is deliberately restricted even on a live page.
:targetNeeds the document's URL fragment.
:checked, :disabled, :required, :read-onlyThese DO work. They are backed by attributes in the markup, so they match on a parsed document like any attribute selector.

These read state that only exists on a rendered page with a user in front of it. Against a parsed document they always return nothing, which is not a bug in your selector.

Specificity contributions

Constructa (id)b (class)c (type)
#id100
.class010
[attr]010
pseudo-class, e.g. :first-child010
type, e.g. div001
pseudo-element, e.g. ::before001
*000
combinators ( > + ~ space )000
:where()000
:is(), :not(), :has()most specific argumentmost specific argumentmost specific argument
:nth-child(An+B of S)0 plus S1 plus S0 plus S

Specificity is a triple compared left to right, and it is computed per selector, not across a comma-separated list. Each selector in a list carries its own value.

Frequently asked questions

Why does table > tr never match?

Because the HTML parser inserts a tbody element into any table that does not have one, so the rows are never direct children of the table. Use table > tbody > tr, or just table tr. The same markup parsed as XML keeps the rows where you wrote them, which is why the two parse modes disagree.

What is the difference between :is() and :not() when a branch is invalid?

It changes whether you get an error or a silently wrong answer. :is(), :where() and :has() take forgiving selector lists: a branch that does not parse is dropped and the rest still match, with no error reported anywhere. :not() is not forgiving, so an invalid argument throws a SyntaxError for the whole selector. A typo inside :is() is therefore much harder to notice than the same typo inside :not().

How is specificity actually compared?

As three numbers compared left to right, not as a base-10 figure. (1,0,0) beats (0,9,9) no matter how many classes are on the other side. Specificity is only consulted when two rules have the same origin and importance, and it is computed for each selector in a comma list independently.

Use the tool