Selectors and cascade
CSS selectors define which elements a rule applies to. The principal categories: type selectors (div, p), class selectors (.btn), ID selectors (#header), attribute selectors ([type="text"]), pseudo-classes (:hover, :nth-child(2n)), pseudo-elements (::before, ::after), combinators (descendant, child, sibling). The cascade — the algorithm that determines which rule wins for each property — is governed by origin (user agent, user, author), specificity (a numeric weight derived from selector composition), and order (later rules override earlier of equal specificity). Custom properties (CSS variables) admit substantial parameterisation and theming. The combination — substantial selector grammar, the cascade for conflict resolution, custom properties, the inheritance mechanism — is the substance of CSS’s targeting surface.
A complete rule
selector {
property: value;
property: value;
}
/* Example: */
.btn {
background: blue;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
}
Multiple selectors share a rule via comma:
h1, h2, h3 {
font-family: Georgia, serif;
}
Type, class, and ID selectors
/* Type (element name): */
p { color: gray; }
button { cursor: pointer; }
/* Class: */
.button { padding: 0.5rem; }
.btn-primary { background: blue; }
/* ID: */
#header { background: black; }
/* Combined: */
button.btn-primary { font-weight: bold; }
.card.featured { border: 2px solid gold; }
The conventional discipline:
- Class selectors — for substantial reusability.
- Type selectors — for substantial element-wide rules.
- ID selectors — rare; admit substantial specificity that admits substantial maintenance burden.
Attribute selectors
[type="text"] { ... } /* exact match */
[type] { ... } /* attribute exists */
[class~="featured"] { ... } /* word in space-separated list */
[lang|="en"] { ... } /* en or en-* */
[href^="https://"] { ... } /* starts with */
[href$=".pdf"] { ... } /* ends with */
[href*="example.com"] { ... } /* contains */
[data-status="active" i] { ... } /* case-insensitive (CSS Selectors L4) */
The mechanism admits substantial pattern-based targeting — particularly for forms and data attributes.
Combinators
The principal combinators:
/* Descendant (any depth): */
article p { ... } /* p inside article */
/* Child (direct child only): */
nav > ul { ... } /* ul direct child of nav */
/* Adjacent sibling: */
h1 + p { ... } /* p immediately after h1 */
/* General sibling: */
h1 ~ p { ... } /* any p that follows h1 */
<article>
<h1>Title</h1>
<p>First paragraph (matches h1+p, h1~p)</p>
<p>Second paragraph (matches only h1~p)</p>
</article>
Pseudo-classes
State-based selection:
/* User interaction: */
a:hover { color: red; }
a:active { color: orange; } /* during click */
a:focus { outline: 2px solid blue; }
a:focus-visible { outline: 2px solid blue; } /* keyboard focus */
a:visited { color: purple; }
button:disabled { opacity: 0.5; }
input:checked { background: green; }
input:placeholder-shown { color: gray; }
/* Form validation: */
input:valid { border-color: green; }
input:invalid { border-color: red; }
input:required { ... }
input:optional { ... }
input:read-only { background: lightgray; }
/* Structural: */
:first-child { ... }
:last-child { ... }
:only-child { ... }
:nth-child(2n) { ... } /* even */
:nth-child(2n+1) { ... } /* odd */
:nth-child(3n+1) { ... } /* 1, 4, 7, ... */
:nth-of-type(odd) { ... } /* counts only same type */
:first-of-type { ... }
:empty { ... } /* no children, no text */
/* Negation: */
:not(.excluded) { ... }
:not(p, h1) { ... } /* multiple */
/* Logical (CSS Selectors L4): */
:is(h1, h2, h3) a { color: red; } /* matches any */
:where(h1, h2, h3) a { color: red; } /* like :is but no specificity */
:has(> img) { ... } /* has direct child img */
/* Tree-relational: */
:root { ... } /* the root element (html) */
:target { ... } /* element matching URL fragment */
:default { ... } /* default form element */
The :is() and :where() admit substantial selector simplification:
/* Without :is: */
header a:hover, main a:hover, footer a:hover { color: red; }
/* With :is: */
:is(header, main, footer) a:hover { color: red; }
/* :where (no specificity): */
:where(header, main, footer) a:hover { color: red; }
Pseudo-elements
Style parts of an element:
::before { content: "→ "; } /* before content */
::after { content: " ←"; } /* after content */
::first-letter { font-size: 2em; } /* first letter (drop cap) */
::first-line { font-weight: bold; } /* first line */
::selection { background: yellow; } /* selected text */
::placeholder { color: gray; } /* input placeholder */
::marker { color: red; } /* list bullet */
The content is required for ::before and ::after:
.icon::before {
content: "★";
margin-right: 0.5em;
}
.required label::after {
content: " *";
color: red;
}
Specificity
Specificity is a four-component value (a, b, c, d):
- a — inline styles (
style="..."). - b — IDs (
#id). - c — classes, attributes, pseudo-classes (
.cls,[attr],:hover). - d — type selectors and pseudo-elements (
p,::before).
p { ... } /* (0,0,0,1) */
p.intro { ... } /* (0,0,1,1) */
.intro { ... } /* (0,0,1,0) */
#main p.intro { ... } /* (0,1,1,1) */
nav a:hover { ... } /* (0,0,1,2) */
[type="text"] { ... } /* (0,0,1,0) */
::before { ... } /* (0,0,0,1) */
* { ... } /* (0,0,0,0) */
:not(p) { ... } /* the not is unmatched; argument's specificity counts */
:is(p, h1) { ... } /* highest of arguments */
:where(p, h1) { ... } /* always 0 */
The principal rule: higher specificity wins over lower; equal specificity → last rule wins.
.card { color: blue; } /* (0,0,1,0) */
p.card { color: red; } /* (0,0,1,1) — wins */
!important
The !important overrides specificity:
.text { color: red !important; }
The conventional discipline avoids !important — admit substantial debugging difficulty. Reserve for:
- Utility classes — explicit overrides.
- User stylesheets — substantial preference enforcement.
Inheritance
Some properties inherit from parent to child:
| Inherited (typical) | Not inherited (typical) |
|---|---|
color, font-*, line-height | background, border |
text-align, text-indent | margin, padding |
visibility, cursor | width, height |
letter-spacing, word-spacing | display, position |
The inherit, initial, unset, revert keywords admit explicit control:
.card {
color: inherit; /* inherit parent's color */
border: initial; /* CSS default */
margin: unset; /* inherit if inheritable, else initial */
background: revert; /* user-agent default */
}
Custom properties (variables)
:root {
--color-primary: blue;
--color-bg: white;
--spacing: 1rem;
--font-base: system-ui, sans-serif;
}
.card {
color: var(--color-primary);
background: var(--color-bg);
padding: var(--spacing);
font-family: var(--font-base);
}
/* Fallback: */
.card {
color: var(--color-primary, blue); /* fallback to blue if not set */
}
/* Scoped: */
.theme-dark {
--color-primary: cyan;
--color-bg: black;
}
Custom properties:
- Inherit like other CSS properties.
- Cascade — admit substantial scoped overrides.
- Dynamic — JavaScript can read/write via
element.style.setProperty().
The mechanism admits substantial theming and substantial responsive design.
The cascade
The cascade resolves conflicts among multiple matching rules:
- Origin and importance —
!importantuser >!importantauthor > author > user > user-agent. - Specificity — higher wins.
- Order — later wins for equal specificity.
/* Author stylesheet: */
p { color: blue; } /* (0,0,0,1) */
.note { color: green; } /* (0,0,1,0) — wins for class=note */
p { color: red; } /* (0,0,0,1) — wins for plain p (later) */
For cascade layers (CSS Cascade L5):
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer base {
body { font-family: sans-serif; }
}
@layer components {
.card { padding: 1rem; }
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
}
The mechanism admits substantial control over specificity — layers later in the list win regardless of specificity.
Common patterns
BEM (Block-Element-Modifier)
.card { ... } /* block */
.card__header { ... } /* element */
.card__body { ... }
.card--featured { ... } /* modifier */
.card--featured .card__header { ... }
The convention admits substantial component-style class organisation; the underscores and double-dashes admit substantial readability.
Utility classes (Tailwind-style)
.flex { display: flex; }
.gap-2 { gap: 0.5rem; }
.p-4 { padding: 1rem; }
.text-center { text-align: center; }
.text-blue-500 { color: #3b82f6; }
<div class="flex gap-2 p-4 text-center text-blue-500">
Content
</div>
The conventional contemporary approach via Tailwind CSS admits substantial productivity.
Scoped state via class
.menu { display: none; }
.menu.is-open { display: block; }
menu.classList.toggle("is-open");
The pattern admits substantial state-based styling.
:has() for parent selection
/* Style parent based on child: */
.card:has(img) { padding: 0; }
.card:has(.featured-tag) { border-color: gold; }
form:has(input:invalid) button { opacity: 0.5; }
The :has() (CSS Selectors L4; widely supported since 2023) admits substantial parent selectors — substantial reduction in JavaScript-driven styling.
Theme variables
:root {
--color-bg: white;
--color-text: black;
--color-accent: blue;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #111;
--color-text: #eee;
--color-accent: #6cf;
}
}
body {
background: var(--color-bg);
color: var(--color-text);
}
Responsive variables
:root {
--columns: 1;
--gap: 1rem;
}
@media (min-width: 768px) {
:root {
--columns: 2;
--gap: 1.5rem;
}
}
@media (min-width: 1200px) {
:root {
--columns: 3;
--gap: 2rem;
}
}
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
gap: var(--gap);
}
Component-scoped classes
.button {
/* default */
background: white;
color: black;
border: 1px solid;
}
.button.primary {
background: blue;
color: white;
border-color: blue;
}
.button.danger {
background: red;
color: white;
border-color: red;
}
.button:disabled,
.button[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
Pseudo-class chaining
nav a:hover:not(.active) {
color: gray;
}
input:not(:placeholder-shown):invalid {
border-color: red; /* only show invalid after typing */
}
button:focus-visible:not(:disabled) {
outline: 2px solid blue;
}
nth-child patterns
li:nth-child(odd) { background: #f0f0f0; } /* zebra striping */
li:nth-child(even) { background: white; }
li:nth-child(3n) { color: red; } /* every third */
li:nth-child(3n + 2) { color: blue; } /* 2, 5, 8, ... */
li:nth-child(-n + 3) { font-weight: bold; } /* first 3 */
li:nth-last-child(-n + 3) { font-style: italic; } /* last 3 */
tr:nth-child(odd) td { background: #f8f8f8; }
Attribute-driven styling
[data-status="active"] { color: green; }
[data-status="pending"] { color: orange; }
[data-status="banned"] { color: red; }
[disabled], [aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
a[href^="https://"]::before {
content: "🔒";
margin-right: 0.25em;
}
a[href$=".pdf"]::after {
content: " (PDF)";
color: gray;
}
Form validation styling
input:not(:placeholder-shown):invalid {
border-color: red;
background: #fee;
}
input:not(:placeholder-shown):valid {
border-color: green;
}
/* Show error message after invalid: */
input:not(:placeholder-shown):invalid + .error-message {
display: block;
}
:where() for low-specificity reset
:where(h1, h2, h3, h4, h5, h6) {
margin: 0;
font-weight: normal;
}
The :where() admits zero specificity — substantial reset that admits substantial overriding.
Combining selectors with attributes
button[type="submit"].primary { ... }
input[type="text"][required] { ... }
a:not([href]) { color: gray; pointer-events: none; }
currentColor
The currentColor keyword refers to the element’s color:
.icon {
color: blue;
border: 1px solid currentColor; /* matches color */
fill: currentColor; /* SVG fill */
}
The mechanism admits substantial colour propagation through nested elements.
Shadow DOM and :host
For Web Components:
:host { display: block; } /* style the component itself */
:host(.large) { font-size: 2rem; } /* state-based */
:host-context(.dark) { background: black; } /* context-based */
A note on the conventional discipline
The contemporary CSS selector advice:
- Use class selectors for substantial reusability.
- Use semantic naming (BEM or similar).
- Avoid
!important— admit substantial debugging burden. - Avoid IDs in CSS — admit substantial specificity.
- Use custom properties for theming and consistency.
- Use
:is()and:where()for substantial selector grouping. - Use
:has()for parent selection (modern browsers). - Use
:focus-visiblefor keyboard-only focus styles. - Use cascade layers for substantial source ordering.
- Use attribute selectors for state-based styling (
[aria-*],[data-*]). - Use
prefers-color-schemefor dark mode. - Avoid universal selector (
*) in hot paths — admits substantial computation.
The combination — substantial selector grammar, the cascade for conflict resolution, custom properties, inheritance, the :is()/:where()/:has() substantial extensions, the cascade-layers mechanism — is the substance of CSS’s targeting surface. The discipline produces maintainable stylesheets with substantial flexibility for substantial design systems.