Part 1: Selectors, Specificity, and the Cascade
Introduction
CSS (Cascading Style Sheets) is what I pick up immediately after writing HTML. The two are inseparable in practice β the markup gives structure, CSS gives it visual form. But CSS has subtleties that take time to internalise. The cascade, specificity, and inheritance explain why a style you wrote seems to have no effect, or why removing one rule breaks three unrelated things. Once those mechanics click, CSS stops being frustrating and becomes predictable.
This part covers how CSS works at the engine level, every selector type I use day-to-day, and how the browser resolves conflicts between competing rules.
How to Apply CSS
There are three ways to attach CSS to an HTML document.
External Stylesheet (preferred)
<link rel="stylesheet" href="styles.css" />The stylesheet is a separate .css file, cached by the browser across pages, and easy to maintain. This is what I use for every project.
<style> Element
<style> Element<head>
<style>
body {
font-family: sans-serif;
}
</style>
</head>Useful for page-specific overrides or critical above-the-fold styles to avoid a render-blocking request.
Inline Styles (avoid for general use)
Inline styles have the highest specificity short of !important, making them difficult to override. They are appropriate when CSS is generated dynamically (e.g., a JavaScript-driven colour value), not for general styling.
CSS Rule Anatomy
Selector β targets the element(s) to style
Declaration block β
{ ... }contains one or more declarationsDeclaration β a
property: valuepair ending with;
Selectors
Type Selector
Targets all elements of a given tag name.
Class Selector
Targets elements with a specific class attribute value. The most common selector in everyday CSS.
A single element can have multiple classes separated by spaces.
ID Selector
Targets a single element with a specific id.
I use ID selectors sparingly in CSS β their specificity is much higher than class selectors, making them hard to override. IDs are better reserved for JavaScript targeting and fragment links.
Universal Selector
Matches every element.
The most common use: applying box-sizing: border-box globally. In isolation it is fine; avoid using it for broad style rules as it matches everything in the document.
Attribute Selector
Targets elements based on the presence or value of attributes.
Pseudo-Class Selectors
Pseudo-classes target elements in a specific state.
Pseudo-Element Selectors
Pseudo-elements create virtual sub-elements for styling.
The double-colon :: is the CSS3 syntax for pseudo-elements. Single colon : works too for historical reasons, but :: is the modern standard.
Combinator Selectors
Combinators select elements based on their relationship to other elements.
Specificity
When multiple rules target the same element and the same property, specificity determines which value wins.
How Specificity is Calculated
Specificity is a three-number score (A, B, C):
Inline style (style="...")
A
ID (#id)
A
Class (.class), pseudo-class (:hover), attribute ([type])
B
Type (p, h2), pseudo-element (::before)
C
Universal (*), combinators ( , >, +, ~)
Nothing
Higher A beats lower A regardless of B and C; A ties compare B; B ties compare C.
Examples
For a <p class="warning">, the text is orange because .warning has higher specificity.
!important
!important!important overrides the entire specificity system. It should be used as a last resort β browser resets, utility classes, or third-party overrides. Once !important appears in a codebase, the only way to override it is with another !important, creating an escalating mess.
The Cascade
The cascade is the algorithm the browser uses to determine which value applies when multiple declarations target the same property on the same element.
The cascade considers three factors, in priority order:
1. Origin and Importance
Origin
Normal
!important
User agent (browser defaults)
Lowest
High
User styles (browser settings)
Medium
Higher
Author styles (your CSS)
High
Highest
Your CSS overrides browser defaults, which is why <button> looks different once you style it.
2. Specificity
Higher specificity wins when origins are the same and neither uses !important.
3. Order of Appearance
When two rules have the same origin and specificity, the last one wins.
This is why the order of <link> tags and @import declarations matters.
Inheritance
Some CSS properties inherit their value from a parent element automatically; others do not.
Inherited by default (typography-related): color, font-family, font-size, font-weight, font-style, line-height, letter-spacing, text-align, text-transform, visibility, cursor
Not inherited by default (box/layout-related): margin, padding, border, background, width, height, display, position, overflow
Controlling Inheritance
CSS Reset and Normalisation
Browsers ship with their own default styles (the user-agent stylesheet). <h1> has a large font size and margin. <ul> has left padding and bullets. <button> has a border and background. These defaults differ slightly between browsers.
My Minimal Reset
This is the reset I paste into every project's styles.css:
Key decisions here:
box-sizing: border-boxβ padding and border are included in element width/height, not added to it. This is the model that matches how most developers think about sizing.margin: 0; padding: 0β removes browser spacing so I start from a known baseline.font: inheriton form elements β browsers default form elements to their own font, which often does not match the page font.max-width: 100%on media β prevents images from overflowing their container.
The :root Selector and Global Variables
:root Selector and Global VariablesThe :root pseudo-class targets the document root element (<html>). It is where I define CSS custom properties (variables) that apply across the entire page.
Custom properties are covered in depth in Part 5, but defining them at :root is a pattern worth establishing from the start.
Summary
Type selector
Targets all elements of that tag
Class selector
Most common β reusable, composable
ID selector
High specificity β use sparingly in CSS
Attribute selector
Target by attribute presence or value
Pseudo-class
Element state (:hover, :focus, :nth-child)
Pseudo-element
Virtual sub-elements (::before, ::after)
Combinators
Structural relationships (descendant, child, sibling)
Specificity
(id, class/pseudo-class/attr, type) β higher wins
!important
Overrides specificity β use as last resort
Cascade order
Origin β specificity β source order
Inheritance
Typography inherits; box model does not
box-sizing: border-box
Apply globally β makes sizing intuitive
Up Next
Part 2 covers the box model, display modes, and positioning β the foundation of how elements take up and share space on a page.
Last updated