Polyglot
Languages Web (HTML / CSS / JS) css selectors
Web (HTML / CSS / JS) § css-selectors

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-heightbackground, border
text-align, text-indentmargin, padding
visibility, cursorwidth, height
letter-spacing, word-spacingdisplay, 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:

  1. Origin and importance!important user > !important author > author > user > user-agent.
  2. Specificity — higher wins.
  3. 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-visible for keyboard-only focus styles.
  • Use cascade layers for substantial source ordering.
  • Use attribute selectors for state-based styling ([aria-*], [data-*]).
  • Use prefers-color-scheme for 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.