Choosing What to Style
Everything CSS does begins with picking elements, and there are about eight selectors worth knowing out of the hundred that exist. Try each against a real document and watch exactly which elements light up.
Worth reading first: Three Ways In, One You Should Use
Everything CSS does begins with picking elements, and there are about eight selectors worth knowing out of the hundred that exist. Try each against a real document and watch exactly which elements light up.
Type selectors match every element of a kind
The simplest selector is an element name, and it matches every one of them in the document.
p { line-height: 1.6; }h1 { font-size: 2rem; }img { max-width: 100%; }body { margin: 0; }These are right for site-wide defaults — the base look of paragraphs, headings, links. They become a problem the moment you want some paragraphs different, because there is no way to say which.
The universal selector * matches everything. It has one common use, and otherwise you should be suspicious of it.
*, *::before, *::after { box-sizing: border-box; }a { outline: 2px solid; }
Matches 3 elements
A type selector. Every anchor in the document, wherever it sits. Simple, and the reason a bare type selector is rarely what you want on a real page — it reaches things you have not thought about yet.
Classes are the ones you will use constantly
A class is a label you put on elements yourself, and a class selector is a full stop followed by the name. This is the workhorse — most real stylesheets are mostly classes.
<p class="intro">The first paragraph.</p><p>An ordinary one.</p><button class="btn btn-primary">Send</button>.intro { font-size: 1.2rem; color: #555; }.btn { padding: 0.5rem 1rem; border-radius: 6px; }.btn-primary { background: #3e7f5c; color: white; }An element can carry several classes, separated by spaces, and every matching rule applies. That button gets both .btn and .btn-primary — shared styling plus a variant, which is the single most useful pattern in CSS.
Apply the same class to fifty elements. Change one rule, change all fifty.
You decide what matches. No accidental reach into elements you had not thought about.
Several classes combine on one element, so shared and variant styles can live separately.
class="warning" survives a redesign; class="red" becomes a lie the day warnings turn orange.
IDs match one element, and cause trouble later
An id is a unique name for one element, and the selector is a hash.
<div id="main-nav">…</div> #main-nav { background: #f5f5f5; }An id must be unique in the document — the same id twice is invalid, and behaviour with things like #fragment links becomes undefined.
IDs are genuinely useful for
Link targets (href="#contact"), joining a label to its input, and being found by JavaScript. All three are about identity, which is what an id is for.
For styling, prefer a class
An id beats any number of classes on specificity, so styling with one starts an escalation you lose. It is also unique by definition, so the moment you want a second of something you are rewriting rather than reusing.
Both can coexist happily: <section id="contact" class="panel"> — the id for linking to, the class for styling.
Descendant and child combinators are different
Combinators describe a relationship between elements. Two of them cover almost everything, and confusing them is the most common reason a selector matches nothing.
nav a { } /* descendant: an a ANYWHERE inside a nav */nav > a { } /* child: an a that is a DIRECT child of a nav */h2 + p { } /* adjacent sibling: the p IMMEDIATELY after h2 */h2 ~ p { } /* general sibling: every p after h2, same parent */The first two are the ones to be careful with. Given a nav containing a list containing links, nav a matches the links and nav > a matches nothing at all — the anchors are children of the li, not of the nav.
<nav> ← nav <ul> ← a CHILD of nav <li> ← a child of ul, a DESCENDANT of nav <a> ← a descendant of nav, not a child </li> </ul></nav>Pseudo-classes match a state, not an element
A pseudo-class is a colon and a keyword, and it matches elements in a particular condition — hovered, focused, first of their kind.
a:hover { text-decoration: underline; }a:focus-visible { outline: 2px solid #3e7f5c; }button:disabled { opacity: 0.5; }input:checked { } li:first-child { }li:last-child { border-bottom: none; }li:nth-child(2n) { background: #fafafa; } /* every second one */ p:not(.intro) { } /* every p WITHOUT that class */The pointer is over it. Note that this does not exist on touch devices — never hide anything important behind hover alone.
Focused via the keyboard. Prefer this to :focus, which also fires on mouse clicks and produces outlines people find noisy.
By position. 2n is every second, odd and even work, 3 is the third exactly.
Everything that does NOT match. Excellent for "all of them except the last".
Related but different: pseudo-elements use two colons and create something that is not in your HTML at all.
.quote::before { content: "\201C"; font-size: 2em; }p::first-line { font-variant: small-caps; }::selection { background: #dbefdb; }::before and ::after need a contentproperty, even if it is empty, or nothing is generated. And their content is not real text in the document — it is not always announced by screen readers and cannot be found by the browser's in-page search, so decoration only.
Key takeaways
- A type selector matches every element of that name — right for site-wide defaults, useless for "only these ones".
- Classes are the workhorse: reusable, composable, and you decide what matches.
- An element can carry several classes; shared styling plus a variant is the most useful pattern in CSS.
- Name classes for purpose, not appearance. .warning survives a redesign; .red becomes a lie.
- IDs are for link targets, labels, and JavaScript. For styling they win specificity fights you did not want to start.
- A space means descendant (any depth); a > means direct child. Confusing them is the top cause of a selector matching nothing.
- Long descendant chains are fragile, slow, and hard to override. Two levels is plenty; a class is usually better.
- Pseudo-classes match a state: :hover, :focus-visible, :nth-child(), :not().
- :hover does not exist on touch devices, so never hide anything important behind it.
- Never remove a focus outline without replacing it — it is how keyboard users know where they are.
- Pseudo-elements use :: and need a content property; their text is decoration and is not reliably announced or searchable.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.