#Selectors: type, class, id, attribute and universal
AddedA selector is a pattern the browser tests against every element; the rule's declarations apply to every element that matches. The building blocks are simple selectors: a type selector (p) matches by tag name, a class selector (.note) by a word in the class attribute, an id selector (#intro) by the id, an attribute selector ([data-state="open"]) by any attribute, and the universal selector (*) matches every element.
Simple selectors written without spaces form a compound selector, and every part must match the same element: a.btn[href] is "an <a>, with class btn, that has an href". A space between them would mean something completely different (a descendant, see Combinators).
Mental model: each simple selector is a filter, and a compound selector stacks filters on one element. In practice you style almost everything with classes: they're reusable, they say what a thing is in your design, and they keep specificity low and equal. Ids are unique per page and very specific, so they're better as link targets and JavaScript hooks than as styling hooks.
* { box-sizing: border-box; } /* universal */
p { color: navy; } /* type */
.note { background: #fef9c3; } /* class */
#intro { font-weight: 700; } /* id */
[data-state="open"] { outline: 2px solid teal; } /* attribute */
a.btn[href] { color: crimson; } /* compound */<p id="intro">Intro paragraph</p>
<p class="note">A note</p>
<div class="note" data-state="open">An open panel</div>
<a class="btn" href="#">A real link</a>
<a class="btn">An anchor without href</a>*matches every element, so all five elements (andbody's other descendants) computebox-sizing: border-box.pmatches both paragraphs by tag name: both are navy,rgb(0, 0, 128)..notematches anything whoseclasslist containsnote, regardless of tag: the paragraph and the div both get the yellow background.#intromatches the one element withid="intro": font-weight700.[data-state="open"]matches the div by its attribute: it gets the teal outline.a.btn[href]is a compound selector: all three conditions on one element. The first link matches and is crimson. The second<a>has the class but nohref, so the compound fails and it keeps its normal colour.
Attribute selectors can do more than test equality. The operators match the value in different ways, and a trailing i makes the comparison case-insensitive:
| Selector | Matches when the attribute… |
|---|---|
[href] | exists, with any value |
[type="email"] | is exactly email |
[class~="warn"] | is a space-separated list containing the word warn |
[lang|="en"] | is exactly en or starts with en- |
[href^="https://"] | starts with https:// |
[href$=".pdf"] | ends with .pdf |
[href*="example"] | contains example anywhere |
[data-size="large" i] | equals large, ignoring case |
a[href^="https://"] { color: green; }
a[href$=".pdf"] { color: crimson; }
a[href*="example"] { font-style: italic; }
[class~="warn"] { background: #fee2e2; }
[class="warn"] { outline: 2px solid crimson; }
[lang|="en"] { text-decoration: underline; }
[data-size="large" i] { font-size: 20px; }<a id="ext" href="https://example.com/">external</a>
<a id="pdf" href="/files/cv.pdf">a PDF</a>
<p id="warn" class="box warn">class="box warn"</p>
<p id="gb" lang="en-GB">lang="en-GB"</p>
<p id="es" lang="es">lang="es"</p>
<p id="big" data-size="LARGE">data-size="LARGE"</p>^="https://"matches the external link (green);$=".pdf"matches the PDF link (crimson). These are handy for marking external links or file types.*="example"matches only the first link, whose URL contains "example": italic. The PDF link staysnormal.[class~="warn"]matchesclass="box warn"becausewarnis one of the space-separated words, the same thing.warndoes.[class="warn"]compares the whole string"box warn"and fails, so no outline.[lang|="en"]matchesen-GB(it's designed for language codes) but notes.[data-size="large" i]matchesLARGEbecause of theiflag:20px.
In HTML documents, tag names are case-insensitive (DIV matches <div>), but class names and ids are case-sensitive (.Note doesn't match class="note"). And an id that starts with a digit is valid HTML, but #1st is not a valid CSS selector, so the whole rule is thrown away; you have to escape the digit.
DIV.note { color: green; } /* tag case doesn't matter */
.Note { background: #fee2e2; } /* class case does */
#1st { color: crimson; } /* invalid selector: the whole rule is dropped */
#\31 st { font-weight: 700; } /* "\31 " is the escaped digit 1 */<div class="note" id="n">class="note"</div>
<p id="1st">id="1st"</p>DIV.notematches the lowercase<div>: tag names in HTML aren't case-sensitive, so it's green..Notedoesn't matchclass="note": the background stays transparent. Class and id matching is case-sensitive.#1stis a syntax error in CSS (an identifier can't start with a digit), so the browser drops the rule while parsing; it isn't even indocument.styleSheets. The paragraph doesn't turn crimson.#\31 stwrites the "1" as its code point (\31plus a terminating space), which is valid, and it matches: weight700.- In JavaScript,
CSS.escape("1st")returns\31 st, sodocument.querySelector("#" + CSS.escape(id))is safe for any id.
h4, h5:not-a-real-thing, h6 { color: crimson; }
h4 { margin: 0; }<h4>h4</h4><h6>h6</h6>h5:not-a-real-thingis invalid: the browser doesn't know that pseudo-class.- Because selector lists are parsed as one unit, the whole rule is dropped, so
h4andh6, which are perfectly valid, don't get crimson either. - This is a real risk with selectors one browser supports and another doesn't, such as vendor-prefixed pseudo-elements like
::-webkit-…in a shared rule.
.card.featured { color: crimson; }<div class="card featured">A</div>
<div class="card"><p class="featured">B</p></div>
<div class="featured card">C</div>Show answer
A and C. .card.featured (no space) needs both classes on the same element; their order in the attribute doesn't matter.
.card.featured { color: crimson; }<div class="card featured" id="a">A</div>
<div class="card"><p class="featured" id="b">B</p></div>
<div class="featured card" id="c">C</div>- A has both classes on one element: it matches.
- B's classes are split between a parent and a child.
.card.featureddoesn't look across elements, so neither matches. (.card .featured, with a space, would match the<p>.) - C lists the classes in the other order. The class attribute is a set of words, so order is irrelevant: it matches.