Chapter 2: CSS Selectors, Combinators, Specificity & Cascading
CSS selectors target HTML elements to apply styles. Mastering simple selectors, attribute selectors, combinators, specificity calculations, cascading rules, and !important is the key to writing scalable, conflict-free stylesheets.
2.1 Simple CSS Selectors
Simple selectors target elements by tag name, class name, unique ID, or universal patterns:
| Selector Type | Syntax | Description | Example |
|---|---|---|---|
| Universal | * |
Matches every element in the HTML document. | * { box-sizing: border-box; } |
| Element / Type | element |
Matches all HTML elements with the specified tag name. | p { line-height: 1.6; } |
| Class | .class |
Matches all elements with a specific class attribute. |
.btn { border-radius: 8px; } |
| ID | #id |
Matches a single unique element with a specific id attribute. |
#navbar { position: fixed; } |
| Grouping | sel1, sel2 |
Applies the same style declarations to multiple comma-separated selectors. | h1, h2, h3 { font-family: sans-serif; } |
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
/* Universal Selector — applies to every element */
* { box-sizing: border-box; }
/* Element / Type Selector */
p { color: #94a3b8; line-height: 1.7; }
/* Class Selector */
.highlight-box {
background: #1e293b;
border: 1px solid #6366f1;
border-radius: 10px;
padding: 16px;
margin: 12px 0;
}
/* ID Selector */
#main-title { color: #818cf8; font-size: 1.6rem; margin-top: 0; }
/* Grouping Selector — h2 and h3 share the same font style */
h2, h3 { color: #38bdf8; font-family: 'Segoe UI', sans-serif; }
</style>
</head>
<body>
<h1 id="main-title">Simple Selectors Demo</h1>
<h2>Class & Element Selector</h2>
<div class="highlight-box">
<h3>Highlight Box (class selector)</h3>
<p>This paragraph uses the element selector <code>p { color: #94a3b8 }</code>.</p>
</div>
<div class="highlight-box">
<p>Another .highlight-box — same class, same styles applied!</p>
</div>
</body>
</html>
2.2 CSS Attribute Selectors
Attribute selectors target HTML elements based on the presence, value, or substring match of their attributes:
Presence Selector: [attribute]
Selects elements that possess the specified attribute, regardless of its value.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
label { display: block; font-size: 13px; color: #94a3b8; margin-bottom: 4px; }
input {
display: block; width: 100%; padding: 8px 12px;
background: #1e293b; color: #f8fafc;
border: 1px solid #334155; border-radius: 6px;
margin-bottom: 6px; font-family: inherit;
}
/* Style any input that has a required attribute */
input[required] { border-left: 4px solid #ef4444; }
.note { font-size: 11px; color: #64748b; margin: 0 0 16px; }
</style>
</head>
<body>
<h3 style="color:#6366f1;margin-top:0;">[attribute] Presence Selector</h3>
<label>Optional Field (no required attr — normal border):</label>
<input type="text" placeholder="Optional input..." />
<p class="note">No [required] attribute → default left border</p>
<label>Required Field (has required attr — red left border):</label>
<input type="text" placeholder="Required input..." required />
<p class="note">input[required] matched → 4px red left border applied!</p>
</body>
</html>
Exact Value Selector: [attribute="value"]
Selects elements whose attribute value matches the string exactly.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
label { display: block; font-size: 13px; color: #94a3b8; margin-bottom: 4px; }
input {
display: block; width: 100%; padding: 8px 12px;
border-radius: 6px; margin-bottom: 6px; font-family: inherit;
background: #1e293b; color: #f8fafc; border: 1px solid #334155;
}
/* Select text inputs specifically */
input[type="text"] {
background-color: #0f172a;
color: #ffffff;
border: 2px solid #6366f1;
}
.note { font-size: 11px; color: #64748b; margin: 0 0 14px; }
</style>
</head>
<body>
<h3 style="color:#3b82f6;margin-top:0;">[attr="value"] Exact Match Selector</h3>
<label>type="text" (targeted by CSS rule):</label>
<input type="text" value="Text input — dark bg + indigo border" />
<p class="note">input[type="text"] matches → styled with dark bg and indigo border</p>
<label>type="number" (not targeted):</label>
<input type="number" value="42" />
<p class="note">input[type="number"] does not match "text" → no special styling</p>
<label>type="email" (not targeted):</label>
<input type="email" value="user@example.com" />
<p class="note">input[type="email"] does not match "text" → no special styling</p>
</body>
</html>
Substring Matchers: ^=, $=, *=
Matches starts with (^=), ends with ($=), or contains substring (*=).
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
a {
display: block; margin-bottom: 10px; padding: 10px 14px;
border-radius: 6px; background: #1e293b; text-decoration: none; color: #94a3b8;
}
/* Links starting with https:// — green (secure) */
a[href^="https://"] { color: #10b981; border-left: 3px solid #10b981; }
/* Links ending in .pdf — append PDF label */
a[href$=".pdf"]::after { content: " 📄 PDF"; font-size: 11px; opacity: 0.8; }
/* Any element whose data-role contains 'card' */
[data-role*="card"] {
border: 2px solid #6366f1;
box-shadow: 0 4px 20px rgba(99,102,241,0.35);
padding: 14px; border-radius: 8px; margin-top: 16px; background: #1e293b;
}
</style>
</head>
<body>
<h3 style="color:#10b981;margin-top:0;">Substring Attribute Selectors</h3>
<a href="https://example.com">https://example.com (^= starts with https://)</a>
<a href="http://example.com">http://example.com (no match — no style)</a>
<a href="annual-report-2024.pdf">annual-report-2024.pdf ($= ends with .pdf)</a>
<div data-role="product-card">
<strong>data-role="product-card"</strong>
<p style="margin:4px 0 0;font-size:13px;color:#a5b4fc;">*= contains "card" → indigo border + glow!</p>
</div>
</body>
</html>
2.3 CSS Combinators
Combinators define structural relationships between two or more selectors:
| Combinator | Syntax | Relationship | Example Description |
|---|---|---|---|
| Descendant | A B (space) |
Descendant | Selects all <p> elements inside .card at any nesting depth. |
| Child | A > B |
Direct Child | Selects all <li> elements that are direct children of <ul>. |
| Adjacent Sibling | A + B |
Next Immediate Sibling | Selects <p> only if it is placed immediately after an <h2>. |
| General Sibling | A ~ B |
Subsequent Sibling | Selects all <p> elements that follow an <h2> under the same parent. |
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
.section { background: #1e293b; border-radius: 10px; padding: 16px; margin-bottom: 16px; }
/* Descendant combinator: any <p> inside .card (any depth) */
.card p { color: #a5b4fc; font-style: italic; }
/* Child combinator: only DIRECT <li> children of <ul> */
ul > li { color: #34d399; list-style: none; padding-left: 0; }
ul > li::before { content: "✓ "; }
/* Adjacent sibling: <p> immediately after <h3> */
h3 + p { color: #f59e0b; font-weight: 600; }
/* General sibling: ALL <p> that follow <h3> at same level */
h3 ~ p { border-left: 2px solid #6366f1; padding-left: 10px; }
</style>
</head>
<body>
<div class="section">
<strong style="color:#818cf8;">Descendant (.card p) & Child (ul > li)</strong>
<div class="card">
<p>Descendant: this <p> is inside .card → indigo italic!</p>
<ul>
<li>Direct child <li> of ul → green checkmark!</li>
<li>Another direct child li → also green!</li>
</ul>
</div>
</div>
<div class="section">
<strong style="color:#818cf8;">Adjacent (h3 + p) & General Sibling (h3 ~ p)</strong>
<h3>Heading</h3>
<p>Adjacent sibling: immediately after h3 → amber bold!</p>
<p>General sibling: follows h3 at same level → indigo left border!</p>
<p>Also a general sibling → also gets indigo left border!</p>
</div>
</body>
</html>
Modern CSS Feature: :has() (The Relational Parent Selector)
Traditionally, CSS selectors could only target elements downwards or sideways. With :has(), you can style a parent element based on its child contents or states!
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
.card {
background: #1e293b; border: 2px solid #334155;
border-radius: 12px; padding: 16px; margin-bottom: 16px;
}
/* Style a card container if it contains an image */
.card:has(img) { border-color: #6366f1; }
.form-group {
background: #1e293b; border: 1px solid #334155;
border-radius: 8px; padding: 14px; margin-bottom: 12px;
transition: all 0.2s;
}
/* Style a form group if the input inside is currently focused */
.form-group:has(input:focus) {
background: rgba(99, 102, 241, 0.1);
border-color: #6366f1;
}
input { background: transparent; border: none; color: #f8fafc; outline: none; width: 100%; font-family: inherit; padding: 4px 0; }
label { font-size: 12px; color: #94a3b8; display: block; margin-bottom: 4px; }
img { width: 100%; border-radius: 6px; height: 80px; object-fit: cover; }
</style>
</head>
<body>
<h3 style="color:#6366f1;margin-top:0;">:has() — Parent Selector Demo</h3>
<div class="card">
<p style="margin:0;font-size:13px;color:#94a3b8;">Card with no image — normal grey border</p>
</div>
<div class="card">
<img src="https://picsum.photos/seed/css3/400/80" alt="Demo image" />
<p style="margin:6px 0 0;font-size:13px;color:#a5b4fc;">.card:has(img) → indigo border applied!</p>
</div>
<div class="form-group">
<label>Click inside the input field to see the parent highlight ↓</label>
<input type="text" placeholder="Focus me — entire box highlights!" />
</div>
<p style="font-size:12px;color:#64748b;">.form-group:has(input:focus) → parent highlights when child input is focused!</p>
</body>
</html>
2.4 CSS Specificity Calculation & Live Calculator
When multiple conflicting CSS rules target the same element, the browser calculates a 4-tuple Specificity Weight (Inline, ID, Class/Attr/Pseudo, Element) to determine which rule wins:
style="")#nav)Interactive Specificity Calculator
Type any CSS selector below to calculate its exact (Inline, ID, Class, Element) specificity score live!
2.5 Cascading Rules & The !important Exception
The Cascade is the algorithm browsers use to resolve conflicts when multiple styles apply to the same element. It resolves styles in the following order:
- Origin & Importance: User Agent default browser styles < Author normal styles < Author
!importantstyles < User Agent!important. - Specificity Score: Higher specificity weight tuple wins.
- Source Order: When origin and specificity are identical, the rule defined last in the stylesheet wins.
Using !important Responsibly
Adding !important to a CSS declaration forces it to override all normal specificity rules. However, overusing it leads to specificity wars and unmaintainable code!
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; }
button {
padding: 10px 22px; border: none; border-radius: 8px;
cursor: pointer; font-size: 14px; font-weight: 600; color: #fff;
display: block; margin: 8px 0;
}
.note { font-size: 12px; color: #64748b; margin: 0 0 16px; }
/* High specificity class rule */
.button-primary { background-color: #4f46e5; }
/* !important overrides EVERYTHING — even higher-specificity selectors */
.alert-btn { background-color: #ef4444 !important; }
/* This very specific rule still CANNOT beat !important */
body .button-primary.alert-btn { background-color: #10b981; }
</style>
</head>
<body>
<h3 style="color:#f59e0b;margin-top:0;">!important Override Demo</h3>
<button class="button-primary">.button-primary → indigo (normal rule)</button>
<p class="note">background: #4f46e5 from .button-primary</p>
<button class="alert-btn">.alert-btn → red (!important wins)</button>
<p class="note">background: #ef4444 !important — overrides everything</p>
<button class="button-primary alert-btn">Both classes — body .button-primary.alert-btn exists but STILL red!</button>
<p class="note">Even "body .button-primary.alert-btn" (high specificity) cannot beat !important!</p>
</body>
</html>
2.6 Interactive Sandbox — Selectors & Specificity
Test combinators, attribute selectors, and specificity overrides live in the interactive code playground below: