Selectors
Choosing what to style: element names for the defaults, classes for the decisions, and the few combinators worth knowing.
Three ways to choose
An element selector — h1 — styles every one of those on the page. It is the right tool for the base: the size and rhythm that all paragraphs share.
A class selector — .card — styles every element marked class="card". It is the right tool for the decisions: this kind of thing looks like this. One element can carry several classes, so class="card card--wide" gets both sets of rules.
An id selector — #site-title — styles the one element with that id. Ids are for anchors and scripts more than for styling; a class does the same job without the single-use rule.
Classes also survive a redesign in a way that element selectors do not. If every card is a div today and becomes an article tomorrow, a .card rule keeps working and a div rule does not. Styling by role rather than by tag is what makes a stylesheet portable between pages that share a look but not a structure.
Combining selectors
A space between two selectors means "inside": .card p matches every paragraph anywhere inside a card. A > means "directly inside": .card > p matches only paragraphs that are immediate children, not ones nested in another element.
Pseudo-classes add a state. a:hover applies while the pointer is over the link, a:focus while it has keyboard focus, and li:nth-child(2) selects the second item in a list. They are selectors, not properties, so they go before the braces.
Forgetting the dot is the most common selector mistake: card looks for a <card> element, while .card looks for class="card". A comma is also not "and" — .card, .notice styles both independently; .card .notice styles a notice inside a card.
Element, class, and a little grouping
body {
font-family: system-ui, sans-serif;
}
.card {
border: 1px solid #d4d4d8;
border-radius: 12px;
}
.card h2,
.card p {
margin: 0;
}When a selector matches nothing
A selector that styles nothing is usually not a syntax error — the browser ignores a rule it cannot parse, and it does that silently. The common causes are a missing dot before a class, a class name that does not match the markup exactly (class names are case-sensitive), and a rule that is being overridden by a heavier selector. Check the markup first, then the spelling, then the cascade.
A selector is also allowed to match more than you intended: .card p reaches paragraphs inside a nested section, and a bare p rule reaches the footer, the aside and the cookie banner. Before adding a rule, ask which elements on the page it will actually reach — the answer is often wider than the one you had in mind.
The inspector answers this faster than reading the file. Select the element, and the Styles panel lists the rules that did match; if your rule is absent, the selector is wrong. If it is present but crossed out, the selector is right and something else is winning. Those are two different problems, and the panel tells you which one you have before you start editing.
When rules disagree
Two rules can match the same element and set the same property. The browser settles it with specificity: a class beats an element, an id beats a class, and the more specific selector wins whatever order the rules are written in.
The practical habit is to keep specificity low and even. When a style refuses to apply, it is almost always because something more specific is winning — not because the rule is missing.
Specificity is counted by selector type, not by distance or by how the selector reads. .card p and p.card both carry one class and one element, so neither is “more specific” than the other; the later rule wins when they tie. Repeating a class adds to the count, which is why .card.card is a heavier selector than a single class.
Attributes and states
An attribute selector matches by what an element says, not only by its class: [type="email"], [disabled], a[href^="https"]. The last one matches links whose href starts with that text. These are useful when the attribute already carries the meaning.
State selectors let the browser tell you what is happening: :hover, :focus, :focus-visible, :checked, :disabled. :focus-visible is the modern focus ring — it appears for keyboard users without drawing a box around every mouse click.
Try these next
- Style
input[type="email"]differently from the other inputs. - Add a
:focus-visiblerule to the nav links and tab through them with the keyboard. - Use
:not(.muted)to give every notice except the muted one a border.
Class names do the work
Two notices and two buttons share the page. Give every notice a border and every button a class of its own to style — one rule each, so a third notice would be styled automatically.
3 left to do
- The notice class has its own rule
- The button class has its own rule
- The notices have a border
.notice { border: 1px solid #d4d4d8; padding: 12px 16px; } and .button { padding: 10px 16px; border-radius: 10px; }.
Style the nav, including its states
Give the links inside the nav a rule, plus a hover state and a focus state. The HTML is done; the stylesheet is empty.
3 left to do
- A rule targets links inside the nav
- The links have a hover state
- The links have a keyboard focus state
.nav a { ... }, .nav a:hover { ... } and .nav a:focus { ... } are three separate rules. The space in .nav a means "links inside the nav".
Name classes for what a thing is — card, notice, button — not for how it looks. green-text becomes a lie the day the text is red.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
When is a class selector the right choice?
