---
title: Selectors
source: 'https://academia.sh/en/courses/css-fundamentals/selectors'
course: 'Visual Presentation with CSS'
language: en
updated: '2026-08-17T18:09:17+00:00'
license: 'CC BY-SA 4.0'
---

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

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.

```js
// 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.

```css
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:

```css
/* 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.
