Selectors: element, class, ID, and combinators
Part 1's h1 selector styles every <h1> on the page identically. Real pages need more precision than that — this part covers the real selector types that let CSS target exactly the elements that need a given style, and nothing else.
Class selectors: the real, default choice
<article class="product-card">
<h3>Gift Subscription</h3>
<p>One bag a month, with a handwritten note.</p>
</article>.product-card {
border: 1px solid #ddd;
padding: 1.5rem;
}A class selector (a dot, then the class name) targets every element with that real class attribute value — genuinely reusable, since the same class can be applied to as many elements as needed, exactly like both subscription <article> cards on Bright Leaf Coffee's homepage. This is the real, correct default for the overwhelming majority of styling.
ID selectors: real, but genuinely narrow
<header id="site-header">...</header>#site-header {
position: sticky;
top: 0;
}An ID selector (a hash, then the ID) targets the one real element with that id — IDs are meant to be unique per page, so this is inherently a one-element selector. It's not wrong to use, but it's a genuinely narrow tool: reach for it specifically for something that's truly one-of-a-kind on the page (the site header, covered in part 13's sticky positioning), not as a general styling habit — part 3 explains the real, practical reason classes are usually the better default.
Combinators: real relationships between elements
/* descendant: any <a> anywhere inside <nav>, at any depth */
nav a {
text-decoration: none;
}
/* child: only <a> that's a direct child of <nav> */
nav > a {
padding: 0.5rem 1rem;
}
/* adjacent sibling: a <p> immediately after an <h2> */
h2 + p {
margin-top: 0.5rem;
}
/* general sibling: any <p> that comes after an <h2>, same parent */
h2 ~ p {
color: #555;
}These four real combinators express genuinely different relationships — descendant selectors (a space) are the most common and most permissive, matching regardless of nesting depth; child selectors (>) are precise about direct parentage only, useful when a descendant selector would accidentally also match a more deeply nested element that shouldn't be styled the same way.
Attribute selectors: real, useful for form styling specifically
input[type="email"] {
border-color: #4fd1c5;
}
input[required] {
border-left: 3px solid #ff8a3d;
}Attribute selectors target elements by a real HTML attribute and, optionally, its value — genuinely useful for styling the HTML series' own form inputs differently by type, or flagging every required field visually without needing to add a separate class to each one manually.
Grouping selectors: one real rule, multiple targets
h1,
h2,
h3 {
font-family: "Georgia", serif;
}A comma between selectors applies the same real declaration block to all of them — genuinely useful for avoiding real, repeated duplication when several different elements need identical styling.
Styling based on real HTML classes, rather than element tags alone, keeps CSS and structure honestly separate — a .product-card class describes a real role content plays, independent of whether the underlying element happens to be an <article>, a <div>, or something else. This is also why the HTML series' emphasis on genuinely semantic markup pays off directly here: real, meaningful class names on real, correctly-chosen elements make CSS selectors self-explanatory instead of a mess of generic wrapper divs.
A real naming convention worth adopting early
.product-card { }
.product-card__title { }
.product-card__price { }
.product-card--featured { }This is BEM (Block, Element, Modifier) — a real, widely used naming convention where a double-underscore marks a sub-part of a component and a double-hyphen marks a variant. It's not required syntax, just a real, consistent pattern — worth adopting early specifically because it keeps class names predictable and searchable once a real stylesheet grows past a handful of rules, covered again in part 20's architecture discussion.
Next: the cascade and specificity — the real, defined rules for which declaration wins when more than one selector matches the same element.