Lesson 03 / 26
Selectors
Which set the type, class, id, and universal selectors return from the document tree; the compound selector concept, and naming class names by meaning.
Contents
The previous lesson attached the style file to the document. Every rule in the file starts
with a selector, and the selectors written so far have only been an element name: body,
h1, td. This means treating every cell in the measurement table the same way. But the
table has cells marking missing measurements, and those need to look different.
This lesson defines four basic selectors: type, class, id, and universal. Which set each one returns from the document tree is measured with a runnable matcher.
A Selector Returns a Set
A selector has a single job: it looks at the nodes in the document tree and returns a subset. The size of the returned set can be zero; an empty set is not an error, it means only that the rule applies to no element.
The following program builds a shrunk-down tree of the previous course’s station page and matches the four selector kinds against that tree. The tree carries each node’s name, its class list, and its id if it has one — that is the information a selector match needs.
// selector-match.mjs — matches basic selectors on a small document tree const tree = { name: "body", classNames: [], id: null, children: [ { name: "header", classNames: ["masthead"], id: null, children: [ { name: "h1", classNames: [], id: null, children: [] }, { name: "p", classNames: ["summary"], id: null, children: [] }, ]}, { name: "main", classNames: [], id: "content", children: [ { name: "table", classNames: ["measurement-table"], id: null, children: [ { name: "tr", classNames: [], id: null, children: [ { name: "td", classNames: ["value"], id: null, children: [] }, { name: "td", classNames: ["value", "missing"], id: null, children: [] }, ]}, ]}, { name: "p", classNames: ["summary", "note"], id: null, children: [] }, ]}, ], }; function* allNodes(n, path = []) { const ownPath = [...path, label(n)]; yield { node: n, path: ownPath.join(" > ") }; for (const c of n.children) yield* allNodes(c, ownPath); } function label(n) { return n.name + (n.id ? `#${n.id}` : "") + n.classNames.map((s) => `.${s}`).join(""); } // simple selector only: type | .class | #id | * function matches(selector, n) { if (selector === "*") return true; if (selector.startsWith(".")) return n.classNames.includes(selector.slice(1)); if (selector.startsWith("#")) return n.id === selector.slice(1); return n.name === selector; } const selectors = ["*", "p", ".summary", "#content", ".value", ".missing", "td", "#value"]; for (const s of selectors) { const found = [...allNodes(tree)].filter(({ node }) => matches(s, node)); console.log(`${s.padEnd(8)} -> ${found.length} element${found.length === 1 ? "" : "s"}`); for (const f of found) console.log(` ${f.path}`); }
* -> 10 elements
body
body > header.masthead
body > header.masthead > h1
body > header.masthead > p.summary
body > main#content
body > main#content > table.measurement-table
body > main#content > table.measurement-table > tr
body > main#content > table.measurement-table > tr > td.value
body > main#content > table.measurement-table > tr > td.value.missing
body > main#content > p.summary.note
p -> 2 elements
body > header.masthead > p.summary
body > main#content > p.summary.note
.summary -> 2 elements
body > header.masthead > p.summary
body > main#content > p.summary.note
#content -> 1 element
body > main#content
.value -> 2 elements
body > main#content > table.measurement-table > tr > td.value
body > main#content > table.measurement-table > tr > td.value.missing
.missing -> 1 element
body > main#content > table.measurement-table > tr > td.value.missing
td -> 2 elements
body > main#content > table.measurement-table > tr > td.value
body > main#content > table.measurement-table > tr > td.value.missing
#value -> 0 elements
Four Kinds
Type selector writes an element name and returns every element that carries that name.
The p selector returned both paragraphs: one in the masthead block, one in the main
content. A type selector does not look at its position in the document.
Class selector starts with a dot and returns elements that carry that name in their
class attribute. An element can carry more than one class; the cell written as
class="value missing" matched both the .value and .missing selectors. Class names are
a space-separated list, and their order carries no meaning.
Id selector starts with a hash sign, and the id attribute matching returns that
element. Ids must be unique within a document, so an id selector returns at most one
element. #content returned one element.
Universal selector is an asterisk and matches every element. The example tree had ten nodes, and it returned all ten.
The last line shows a distinction: #value returned zero elements, because value is a
class name, not an id. Confusing a dot with a hash sign is a rule that returns an empty
set and produces no warning at all. It is one of the most common reasons a rule fails to
have any effect.
Compound Selector
When simple selectors are written next to each other with no space between them, they form a compound selector, and it returns elements that satisfy all of them at once. In set terms: intersection.
td.missing { color: #8a1c1c; } p.summary.note { font-style: italic; }
The first selector returns cells that are both a td and carry the missing class. In the
example tree, this is a single cell. The second selector asks for all three conditions and
also reaches a single element.
If a compound selector includes a type selector, it is written first: td.missing is
valid, .missingtd is something else. When no type selector is written, the universal
selector is assumed; .missing and *.missing return the same set.
Class or Id
Both are names the author places into the document. The technical difference between them is single: an id is unique, a class is not. Two separate consequences follow from that difference.
An id places an anchor pointing to one single place in the document. A link target
(href="#content"), a label-field relationship (the for attribute), and accessibility
references are all built with an id. These have nothing to do with styling; that is an
id’s actual job.
A class, on the other hand, declares membership in a group. That is exactly what style needs: “this cell carries a missing measurement,” “this paragraph is a summary.” No matter how many times the same statement appears in the document, it should get the same style.
The problem with using an id selector in style shows up not when it fails to work, but when it does work: an id selector’s specificity is higher than a class selector’s, and once written, overriding it requires another id selector. This is worked out with numbers in the cascade-and-specificity lesson. For now, the actionable rule is: style is written with a class, an id is for reference.
A Class Name Names Meaning, Not Appearance
A class name can be chosen in two ways. .red names an appearance; .missing names a
state. Either can be attached to the same cell and give the same result at first.
The difference shows up when the decision changes. If it is decided that missing
measurements should be shown in gray instead of red, the name .red starts lying: a class
named red with a gray color remains. Fixing it requires touching the document itself,
changing the class name everywhere it occurs. With .missing, the only thing that changes
is one declaration’s value; the document stays fixed.
This is the class-name counterpart of the structure-presentation distinction established in the Web Fundamentals and HTML course: the document states what something is, the style file states how it will look.
The station page’s class names have been chosen according to this principle:
/* station.css — step 2: status classes */ .measurement-table td.value { text-align: right; } .measurement-table td.missing { color: #8a1c1c; } .masthead .summary { font-size: 1.125rem; }
The last rule has a space between two selectors, and that space is an operator — not a compound selector, but a relationship statement. The next lesson defines that operator and its relatives.
Summary
- A selector returns a subset from the document tree; returning an empty set is not an error, it only means the rule applies to no element.
- A type selector is written with an element name, a class selector with a dot, an id selector with a hash sign; a universal selector is an asterisk and matches every element.
- When simple selectors are written next to each other with no space, they form a compound selector and return the intersection of their conditions; a type selector is written first.
- An id is unique within a document, and its actual job is being a reference target; style is written with a class, because a style decision applies to more than one element.
- A class name names meaning, not appearance; a name that names appearance forces the document to change too when a style decision changes.
Next Step
The selectors in this lesson looked at the element itself: what its name is, which classes it carries, whether it has an id. Its position in the document tree was never used — yet a request like “only the cells inside the measurement table” requires asking where an element stands. The next lesson defines the combinators that bring tree relationships into a selector, and shows how a selector is evaluated from right to left.
To keep your progress and take notes, Log in
My notes
Log in to take notes.