Skip to content

Chapter 3 of 8

CSS Selectors & the Cascade

CSS selectors are the foundation of styling, targeting HTML elements through several basic mechanisms. The element selector applies to every instance of a tag, the class selector targets any element carrying that class, the ID selector targets the single element with that ID, the universal selector (an asterisk) matches every element, and attribute selectors match elements based on the presence or value of an attribute using patterns like `[attr="value"]`, `[attr^="value"]` for starts-with, `[attr$="value"]` for ends-with, `[attr*="value"]` for contains, and `[attr~="value"]` for word-in-list. Case sensitivity can be controlled with an `i` flag like `[attr="value" i]`. Attribute selectors are particularly powerful for styling form inputs based on type or state without adding extra classes, and they reflect the broader distinction where `id` is unique per page while `class` can appear on multiple elements and one element can carry many classes.

Selectors can be combined with combinators that define relationships between them. The descendant selector (a space) matches any element nested at any depth inside the first, the child selector (`>`) matches only direct children, the adjacent sibling selector (`+`) matches the element immediately following the first, and the general sibling selector (`~`) matches any later sibling. Combinators themselves add no specificity. Pseudo-classes select elements based on state or position without requiring additional markup: `:hover` matches when the mouse is over the element, `:focus` when an element has keyboard focus, `:first-child` and `:nth-child(n)` for positional targeting among siblings, `:not(selector)` to exclude a match, and `:checked` for selected checkboxes and radios. The `:nth-child()` pseudo-class accepts formulas like `odd`, `even`, `3n`, or `3n+1` for selecting every nth item, while `:nth-of-type()` differs by counting only siblings of the same element type, which is useful when elements are mixed within a parent.

Modern pseudo-classes and pseudo-elements expand what is possible with selectors. `:is()` and `:where()` accept lists of selectors and match any element matched by any of them, with `:is()` taking the specificity of its most specific argument and `:where()` always having zero specificity, making `:where()` ideal for low-priority resets that should never override real styles. The `:has()` selector, often called the parent selector, matches elements containing certain children, such as `article:has(h2)`, unlocking patterns that were previously impossible without JavaScript. Focus variants include `:focus`, which matches whenever an element has focus including mouse clicks; `:focus-visible`, which matches only when focus should be visually shown (typically keyboard navigation), avoiding the focus ring on mouse clicks; and `:focus-within`, which matches an ancestor when any descendant has focus. Pseudo-elements, written with double colons, style specific parts of an element: `::before` and `::after` insert generated content before or after the element, often combined with the `content` property holding text, images via `url()`, counters, or attribute values pulled with `attr()`, while `::first-line`, `::first-letter`, and `::placeholder` style the first line, first letter, and placeholder text respectively. Generated content from pseudo-elements is not selectable and not part of the DOM.

When multiple rules target the same element, the cascade determines which wins, using a priority order from highest to lowest: `!important` declarations, origin (author styles beat user styles beat browser defaults), specificity, and source order. Specificity is calculated as a four-part value where inline styles contribute 1000 in the first position, ID selectors 100 in the second, class/attribute/pseudo-class selectors 10 in the third, and element/pseudo-element selectors 1 in the fourth; the selector with the highest value wins, and ties are broken by source order with later rules winning. Because `!important` overrides everything, it should be avoided except in narrow cases. A more modern alternative to specificity wars is CSS cascade layers declared with `@layer`, which establishes a priority order independent of specificity: styles in later layers always win over earlier ones, and unlayered styles beat layered styles, letting you safely use low-specificity utilities without fear of being overridden.

All chapters
  1. 1Foundations of HTML Document Structure
  2. 2Forms, Validation & Accessibility
  3. 3CSS Selectors & the Cascade
  4. 4The Box Model, Display & Positioning
  5. 5Flexbox & CSS Grid Layout
  6. 6Responsive Design, Units & Custom Properties
  7. 7Animations, Transforms & Visual Effects
  8. 8Performance, Loading & Advanced HTML

Drill it

Reading is not remembering. These come from the HTML CSS Fundamentals deck:

Q

What is the purpose of HTML5 semantic elements?

HTML5 semantic elements provide meaningful structure to web pages. They describe the role of their content rather than just its appearance. Examples include &lt...

Q

What is the difference between <section> and <div>?

<section> is a semantic element that represents a thematic grouping of content, typically with a heading. <div> is a non-semantic generic container...

Q

What is the <article> element used for?

The <article> element represents a self-contained composition that could be independently distributed or reused. Examples include:Blog postsNews articlesF...

Q

What is the purpose of the <nav> element?

The <nav> element represents a section of a page that contains navigation links, either within the site or to other sites. It is intended for major naviga...