CSS selectors are the patterns that tell the browser which HTML elements a style rule should apply to. Without selectors there would be no way to target individual elements — all styles would have to be inline. In this lesson you will learn the full range of CSS selectors, from simple element and class selectors to combinators and pseudo-classes, and understand when to reach for each one.
CSS Selector Types
Basic Selectors
| Selector | Syntax | What It Targets |
|---|---|---|
| Element (type) | p |
Every <p> element on the page |
| Class | .card |
Every element with class="card" |
| ID | #header |
The single element with id="header" |
| Universal | * |
Every element — use sparingly |
| Attribute | input[type="email"] |
Elements with a specific attribute value |
| Grouped | h1, h2, h3 |
Applies the rule to all listed selectors |
Combinator Selectors
| Combinator | Symbol | Meaning |
|---|---|---|
| Descendant | A B (space) |
B anywhere inside A (any depth) |
| Direct child | A > B |
B that is an immediate child of A only |
| Adjacent sibling | A + B |
B that immediately follows A |
| General sibling | A ~ B |
All B siblings that follow A |
Pseudo-classes and Pseudo-elements
| Type | Example | When It Applies |
|---|---|---|
| Pseudo-class | a:hover |
User hovers cursor over the element |
| Pseudo-class | li:nth-child(2) |
The second child element in its parent |
| Pseudo-class | input:focus |
Element has keyboard/mouse focus |
| Pseudo-element | p::first-letter |
The first letter of a paragraph |
| Pseudo-element | .card::before |
A generated element inserted before .card |
h1, h2, h3 { font-family: Georgia; }. This follows the DRY principle and keeps stylesheets concise.* { } — scope the universal selector to a container like .modal * { } to limit its performance impact.Basic Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Selector Showcase</title>
<style>
/* Element selector */
p { font-family: system-ui, sans-serif; line-height: 1.6; }
/* Class selector */
.highlight { background: #fef9c3; padding: 2px 6px; border-radius: 3px; }
/* ID selector */
#page-title { color: #1d4ed8; letter-spacing: -0.02em; }
/* Attribute selector */
input[type="text"] {
border: 2px solid #6366f1;
border-radius: 4px;
padding: 6px 10px;
}
/* Pseudo-class */
a:hover { color: #dc2626; text-decoration: underline; }
/* Direct child combinator */
.box > p { margin: 0; color: #374151; font-weight: 500; }
/* Pseudo-element */
article p::first-letter {
font-size: 2.5rem;
font-weight: 700;
color: #7c3aed;
float: left;
line-height: 1;
margin-right: 6px;
}
/* Adjacent sibling */
input[type="text"] + label {
display: block;
margin-top: 6px;
color: #64748b;
font-size: 0.85rem;
}
</style>
</head>
<body>
<h1 id="page-title">CSS Selectors Demo</h1>
<p>This uses <span class="highlight">element and class</span> selectors.</p>
<div class="box">
<p>Direct child of .box — child combinator applies.</p>
<div><p>Grandchild — child combinator does NOT apply.</p></div>
</div>
<article>
<p>Drop-cap via ::first-letter pseudo-element.</p>
</article>
<input type="text" id="name" placeholder="Your name">
<label for="name">Adjacent sibling label</label>
<p><a href="#">Hover this link</a></p>
</body>
</html>
How It Works
Step 1 — Element Selector Sets a Base Style
p { font-family: system-ui; line-height: 1.6; } targets every <p> globally with specificity (0,0,0,1) — the lowest possible, easily overridden by classes or IDs.
Step 2 — Class Selector Adds Contextual Styling
.highlight has specificity (0,0,1,0) — one class beats one element selector. It can be applied to any element type without rewriting the rule.
Step 3 — ID Selector Targets a Unique Element
#page-title has specificity (0,1,0,0). It targets the one element with id="page-title". No class-based rule can override it without using an ID or !important.
Step 4 — Attribute Selector Targets Form Controls
input[type="text"] selects only text inputs, ignoring checkboxes and radio buttons — precision styling without extra classes.
Step 5 — Combinators Establish Relationships
.box > p selects only <p> elements that are direct children of .box. A nested <p> inside a nested <div> inside .box would NOT be selected.
Real-World Example: Navigation Menu
<nav class="site-nav">
<ul class="nav-list">
<li><a href="/" class="nav-link active">Home</a></li>
<li><a href="/about" class="nav-link">About</a></li>
<li><a href="/services" class="nav-link">Services</a></li>
<li class="nav-dropdown">
<a href="/work" class="nav-link">Work</a>
<ul class="dropdown-menu">
<li><a href="/work/web">Web Design</a></li>
<li><a href="/work/branding">Branding</a></li>
</ul>
</li>
</ul>
</nav>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
nav.site-nav {
background: #1e293b;
padding: 0 32px;
position: relative;
}
.nav-list li { display: inline-block; position: relative; }
.nav-link {
display: inline-block;
padding: 16px 18px;
color: #94a3b8;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
font-family: system-ui, sans-serif;
transition: color 0.2s ease;
}
.nav-link:hover { color: #f1f5f9; }
.nav-link.active { color: #818cf8; border-bottom: 2px solid #818cf8; }
.nav-list li + li .nav-link { border-left: 1px solid #334155; }
.nav-dropdown > .dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
background: #1e293b;
min-width: 180px;
border-top: 2px solid #818cf8;
list-style: none;
padding: 8px 0;
}
.nav-dropdown:hover > .dropdown-menu { display: block; }
.dropdown-menu a {
display: block;
padding: 10px 20px;
color: #94a3b8;
text-decoration: none;
font-size: 0.875rem;
font-family: system-ui, sans-serif;
}
.dropdown-menu a:hover { color: #f1f5f9; background: #334155; }
Common Mistakes
Mistake 1 — Overusing ID selectors for styling
❌ Wrong — IDs have very high specificity and cannot be reused:
#submit-btn { background: blue; color: white; padding: 10px 20px; border: none; }
#cancel-btn { background: grey; color: white; padding: 10px 20px; border: none; }
✅ Correct — share common styles via a class:
.btn { color: white; padding: 10px 20px; border: none; cursor: pointer; }
.btn-primary { background: blue; }
.btn-secondary { background: grey; }
Mistake 2 — Descendant vs child combinator confusion
❌ Wrong — accidentally styles deeply nested paragraphs:
.card p { color: crimson; } /* selects ALL p inside .card at any depth */
✅ Correct — target only immediate children:
.card > p { color: crimson; }
Mistake 3 — Wrong pseudo-link order (LVHA)
❌ Wrong — :visited after :hover overrides hover on visited links:
a:link { color: blue; }
a:hover { color: red; }
a:visited { color: purple; }
✅ Correct — follow the LVHA order:
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; }
a:active { color: orange; }
Quick Reference
| Selector | Syntax | Specificity |
|---|---|---|
| Element | p |
(0,0,0,1) |
| Class | .name |
(0,0,1,0) |
| ID | #name |
(0,1,0,0) |
| Attribute | [type="text"] |
(0,0,1,0) |
| Pseudo-class | :hover, :focus |
(0,0,1,0) |
| Pseudo-element | ::before, ::first-letter |
(0,0,0,1) |
| Child combinator | A > B |
Sum of A + B |