This episode dissects CSS selectors: element, class, id, attribute, and combinations between elements. You'll also understand how browsers calculate specificity weight and why more specific selectors always win.

In episode 1 you met the simplest selector: targeting elements by tag name. Now it's time to broaden that skill. Selectors are the key to choosing which elements to style with precision, and CSS provides many ways to do it.
Episode 2 covers two big topics: the types of basic selectors and the specificity hierarchy. The two are closely related. The better you understand a selector's weight, the easier it is to predict which rule wins when two rules clash.
Why does this topic matter? Most of those confusing styling bugs — "I wrote it but nothing changed" — are rooted in a wrong understanding of specificity. Master this episode, and you'll stop memorizing tricks to beat other rules.
The three most common selectors:
h1 { color: navy; }
.btn {
background-color: #2563eb;
color: white;
}
#hero {
padding: 24px;
text-align: center;
}h1 targets all elements with the tag h1; .btn targets all elements with the class btn; #hero targets the single element with the id hero. Classes are reusable, while ids should be unique within a page.
Attribute selectors select based on the presence or value of an attribute:
a[href] { color: #2563eb; }
input[type="text"] { border: 1px solid #ccc; }
a[href^="https"] { font-weight: bold; }
img[alt$=".png"] { border-radius: 8px; }^= means "starts with", $= means "ends with", and *= means "contains". These selectors are useful for targeting external links or specific input types without adding classes.
CSS also allows combining elements:
article p { line-height: 1.7; }
ul > li { list-style: square; }
h1 + p { margin-top: 4px; }
h1 ~ p { color: #374151; }article p selects all p inside article (descendant); ul > li selects li elements that are direct children of ul (child); h1 + p selects a p immediately following an h1 (adjacent sibling); h1 ~ p selects all p elements after an h1 that are siblings.
Specificity is calculated like a three-digit number:
Example:
p { color: gray; }
.text { color: blue; }
#special { color: red; }If an element <p id="special" class="text"> uses all three, it shows red. The weight of #special (1-0-0) is higher than .text (0-1-0) and p (0-0-1). A simple p selector always loses to a more specific class selector.
When two selectors have identical weight, the one written last wins:
.btn { background-color: green; }
.btn { background-color: orange; }An element with the class btn will be orange because the second rule is written after the first. This is why CSS files should be written in a sensible order.
Best practice is to keep specificity low and as flat as possible. Avoid stacking selectors like div#header .nav ul li a — its weight is high and makes overriding hard. Just use one clear class:
.nav-link {
color: #2563eb;
}Pseudo-classes target specific states or positions:
a:hover { color: red; }
li:first-child { font-weight: bold; }
li:last-child { color: #6b7280; }:hover applies when the cursor is over the element, :first-child selects the first element among its siblings, and :last-child the last. Episode 13 will cover pseudo-classes thoroughly.
Warning
Avoid excessive !important. It breaks through all specificity calculations and makes stylesheets hard to maintain. Use it only for extreme cases like overriding third-party library styles.
Selectors like body div.main-content .card p are nearly impossible to override without chaos. The fix: put a class directly on the target element so specificity stays low and predictable.
One missing curly brace makes the rules that follow unreadable. Get into the habit of auto-formatting with npx prettier --write css/style.css before saving.
Key takeaways:
!important is for extreme cases, not daily solutions.In the next episode, episode 3, we'll cover text, color, and background properties — how to set fonts, text color, and background images and colors with real examples. Get your style.css ready for the next exercise.