What is CSS? Syntax, Selectors & the Cascade
How CSS actually applies styles — selectors, the cascade, inheritance, and the mental model everything else in CSS builds on.
CSS Is a Rule Language, Not a Programming Language
HTML gives a page structure and meaning — a heading, a paragraph, a list. CSS (Cascading Style Sheets) is the language that decides how that structure actually looks: colors, spacing, fonts, layout, and every visual decision on the page. The two are deliberately separate. The same HTML document can look completely different under a different stylesheet, and that separation — content in one place, presentation in another — is the entire design philosophy CSS is built around.
Every CSS rule has exactly the same two-part shape: a selector, which says which elements the rule applies to, followed by a declaration block wrapped in curly braces, containing one or more property: value; pairs.
selector {
property: value;
property: value;
}
/* A concrete example */
p {
color: #1a1a1a;
font-size: 16px;
line-height: 1.6;
}Reading that rule aloud: "select every p element, and set its text color to#1a1a1a, its font size to 16 pixels, and its line height to 1.6." Every declaration ends with a semicolon — technically the semicolon on the very last declaration in a block is optional, but omitting it is a common source of bugs the moment you add a new declaration underneath it and forget to add one to the line above, so treat it as required.
Comments in CSS
CSS comments use /* ... */ — there is no single-line // comment syntax in plain CSS (Sass, covered later in this track, does add one, but it is not valid in a.css file loaded directly by the browser).
/* This is a comment. It can span
multiple lines. */
p {
color: red; /* inline comments work too */
}Three Ways to Attach CSS — and Why One of Them Wins
There are exactly three mechanisms for getting CSS onto an HTML page. All three are valid, all three actually work, and real production codebases almost universally settle on one of them for the bulk of their styling.
1. External stylesheet — a separate .css file, linked in
<!-- in the <head> of your HTML document -->
<link rel="stylesheet" href="styles.css">body {
font-family: system-ui, sans-serif;
margin: 0;
}
h1 {
color: #f97316;
}2. Internal (embedded) stylesheet — a <style> block in the document head
<head>
<style>
body {
font-family: system-ui, sans-serif;
}
h1 {
color: #f97316;
}
</style>
</head>3. Inline styles — a style attribute directly on an element
<h1 style="color: #f97316; font-size: 32px;">Welcome</h1>Every real production project reaches for the external stylesheet as the default, and for good reason: it is cached by the browser separately from the HTML (so a repeat visit to any page on the site does not re-download the CSS), it can be shared across every page on a site instead of duplicated inside each one, and it keeps structure and presentation cleanly separated so a designer or front-end engineer can change the entire look of a site without touching a single HTML file.
style attribute has a specificity so high it overrides nearly every external and internal rule automatically, which makes inline styles very difficult to override later without resorting to !important. Reach for inline styles only for one-off, dynamically computed values (a chart bar's width set by JavaScript, for instance) — never as your default styling method.Why not just use a <style> block everywhere?
An internal stylesheet is genuinely useful for small demos, single-file examples, and quick prototypes — you are looking at exactly that pattern in every code example on this page's learning platform. But it does not scale: it cannot be cached independently, it cannot be shared across multiple HTML pages without copy-pasting it into every single one, and a real multi-page site with an internal stylesheet per page quickly turns into dozens of near-duplicate style blocks that drift out of sync with each other.
Selecting Elements — The Basic Vocabulary
Selectors determine which elements a rule targets. This module covers the fundamentals needed to read and write everyday CSS; the full combinator and pseudo-class/pseudo-element vocabulary, plus the complete specificity calculation, gets a dedicated deep dive later in this phase.
/* Type (element) selector — targets every <p> on the page */
p {
color: #333;
}
/* Class selector — targets every element with class="highlight" */
.highlight {
background: yellow;
}
/* ID selector — targets the ONE element with id="site-header" */
#site-header {
position: sticky;
}
/* Universal selector — targets literally every element */
* {
box-sizing: border-box;
}Classes are reusable — the same class name can be applied to as many elements as you like, and a single element can carry multiple classes separated by spaces. IDs are meant to be unique — one per page — and while browsers will not stop you from reusing an ID, doing so is invalid HTML and breaks anything on the page that relies on IDs being unique, including document.getElementById in JavaScript.
<button class="btn btn-primary btn-large">Submit</button>h1, h2, h3 {
font-family: 'Georgia', serif;
margin-top: 0;
}
/* Equivalent to writing three separate rules with identical bodies */The "C" in CSS — Later Rules Win Ties
The cascade is the algorithm the browser runs to decide which declaration actually wins when multiple rules target the same property on the same element. There are three forces that feed into it, in increasing order of power: source order, specificity, and importance. This part covers source order — the simplest of the three, and the one you already have an intuition for.
p {
color: blue;
}
p {
color: red;
}
/* Every <p> renders red. Same selector, same specificity — the browser
simply applies rules in the order they appear, and later ones overwrite
earlier ones for any property they both set. */This is exactly why the order your stylesheets — and the rules within them — are loaded in matters. A common real bug: linking a third-party CSS library after your own stylesheet, which lets the library's styles silently override your own for any selector of matching specificity.
<!-- Loaded first -->
<link rel="stylesheet" href="my-styles.css">
<!-- Loaded second — anything of equal specificity here wins -->
<link rel="stylesheet" href="third-party-library.css">Specificity — A Preview of the Full Story
Specificity is a score the browser calculates for every selector, based on what kinds of selectors it is built from. When two rules with different specificity conflict, the higher-specificity rule wins — regardless of source order, even if the lower-specificity rule was written later in the file. The complete numeric calculation, worked through with real examples, is the entire subject of the CSS Selectors Deep Dive module immediately after this one — for now, the concept to internalise is the rough hierarchy.
/* Lowest — element/type selectors and pseudo-elements */
p { color: black; }
/* Medium — class, attribute, and pseudo-class selectors */
.text { color: blue; }
/* High — ID selectors */
#intro { color: green; }
/* Highest of all (short of !important) — inline styles */
/* <p style="color: purple;"> */p {
color: blue;
}
.text {
color: red;
}
/* <p class="text">Hello</p> renders RED — .text is a class selector,
which is more specific than the plain element selector "p", even
though "p" happens to be written second. */This is the single most common source of "why isn't my CSS applying?" confusion for anyone new to the language — a rule that looks like it should apply, sitting later in the file, gets silently beaten by an earlier rule with a more specific selector. Once specificity is understood precisely (next module in this phase), that confusion mostly disappears.
!important — The Cascade's Escape Hatch, and Why to Avoid It
!important is a modifier you can append to any declaration to make it overrideeverything else targeting that property on that element — regardless of specificity or source order. It sits at the very top of the cascade's power hierarchy: importance beats specificity, and specificity beats source order.
#intro {
color: green;
}
p {
color: red !important;
}
/* <p id="intro">Hello</p> renders RED. Normally the ID selector (#intro)
would win on specificity alone — but !important short-circuits the
entire specificity comparison for this one declaration. */!important exists, overridingit later requires either an even more specific selector combined with another!important, or removing the original entirely — a real maintenance trap that grows worse the more a codebase relies on it.There are a small number of legitimate uses — most commonly overriding inline styles you cannot control (some third-party widgets set styles inline via JavaScript) or a narrowly scoped utility class explicitly designed to always win (a .hidden { display: none !important; } utility, for example, that must never be silently overridden by component-specific styles). As a rule of thumb: if you find yourself reaching for !important to win an argument with your own earlier CSS, the real fix is almost always to lower the specificity of the earlier rule or restructure the selectors, not to escalate.
Inheritance — Which Properties Pass Down the Tree, and Which Don't
Separately from the cascade, CSS has an inheritance mechanism: certain properties, if left unset on a child element, automatically take the computed value of their parent. This is not universal — it is a specific, well-defined set of properties, chosen because it matches how real design actually works: you generally want text-related properties to flow down a whole document from one place, but you very much do not want a parent's border or margin to automatically apply to every element nested inside it.
body {
color: #222;
font-family: 'Helvetica Neue', sans-serif;
font-size: 16px;
line-height: 1.6;
}
/* Every <p>, <span>, <li>, <a>, etc. nested inside <body> automatically
takes this color, font-family, font-size, and line-height — UNLESS
something more specific overrides it further down the tree. */The commonly inherited properties are almost entirely typography- and text-related: color, font-family, font-size, font-weight, line-height, text-align, letter-spacing, and visibility, among a handful of others. Setting these once, high up in the document (commonly on body or html), is the standard way to establish a page's baseline typography without repeating it on every single element.
.card {
border: 1px solid #ccc;
padding: 20px;
margin: 16px;
}
/* An element nested inside .card does NOT automatically get a border,
padding, or margin of its own. Each element's box-model properties
default to zero/none unless explicitly set on THAT element. */border, margin, padding, width, height, background, and display are all non-inherited — which makes sense once you think about what inheriting them would mean: every nested element inside a bordered card would grow its own identical border, compounding visually the deeper the nesting went. Non-inheritance is what keeps layout-affecting properties predictable and scoped to the exact element they are set on.
Forcing inheritance with the inherit keyword
Any property — even one that does not inherit by default — can be forced to take its parent's value using the special inherit keyword as its value.
.card {
border: 1px solid #ccc;
}
.card .nested-box {
border: inherit; /* explicitly copies the parent's border value */
}inherit: initial resets a property to its specification-defined default value (ignoring both the cascade and inheritance entirely), and unset acts like inherit for naturally-inheriting properties and like initial for everything else. All three are used far less often than plain values, but they show up regularly when deliberately undoing a style set higher up the cascade.A CSS Bug at a Portland Furniture E-Commerce Startup
A front-end engineer at a Portland-based furniture e-commerce site is asked to make the "Add to Cart" button's text red on the checkout page only, to match a limited-time sale banner. They add a new rule to the bottom of the site's global stylesheet, confident that appending it at the end means it will win.
.btn {
color: red;
}The button stays exactly the same color it always was. The engineer checks that the file deployed correctly, hard-refreshes, clears cache — the rule is definitely loading, and it is definitely positioned after every other style rule in the file. It still does not apply.
What DevTools reveals
Opening the Elements panel and inspecting the button shows the real cause immediately: an earlier rule, defined with a much more specific selector, is winning the cascade — with a strikethrough visible on the newly added .btn rule showing exactly which declaration beat it.
#checkout-page .cart-controls .btn {
color: #1a1a1a;
}This is a direct, real-world instance of Part 05: an ID selector combined with two class selectors produces a specificity score far higher than a single class selector, and no amount of appending the new rule further down the file can change that — source order (Part 04) only breaks ties, and this was never a tie.
The fix
#checkout-page .cart-controls .btn {
color: red;
}
/* Same selector shape as the rule it needs to beat, added AFTER it in
source order — now it wins fairly, on the cascade's actual rules,
with no !important required. */The team also flags the original overly-specific selector for cleanup — an ID plus two nested classes for something as simple as a button color is exactly the kind of selector that makes every future override this painful. This diagnosis — "my rule is later in the file but still loses" always tracing back to specificity, not source order — is one of the single most common CSS debugging sessions a front-end engineer will run, on every team, for their entire career.
Four Misconceptions About CSS Basics
5 Interview Questions — With Complete Answers
CSS Basics Mistakes Beginners Make Constantly
Rendering Problems You Will Hit — And Exactly Why
🎯 Key Takeaways
- ✓Every CSS rule has two parts: a selector (which elements) and a declaration block of property: value; pairs (what changes).
- ✓CSS can be attached three ways — external stylesheet, internal <style> block, or inline style attribute — with external winning for nearly every real production project.
- ✓The cascade resolves conflicts using three forces, weakest to strongest: source order, specificity, and importance (!important).
- ✓Source order only breaks ties between selectors of EQUAL specificity — a more specific selector written earlier still beats a less specific one written later.
- ✓Specificity is roughly: inline styles > ID selectors > class/attribute/pseudo-class selectors > element/pseudo-element selectors. The full numeric calculation is covered in the next module.
- ✓!important overrides the entire specificity system and should be used sparingly — it is usually a symptom of an unresolved specificity conflict, not a real fix.
- ✓Inheritance is separate from the cascade: a specific set of mostly text-related properties (color, font-family, line-height) pass down to children automatically; box-model properties (margin, padding, border) do not.
- ✓Class selectors are the standard, reusable building block of real-world CSS — reserve IDs for JavaScript hooks and anchor targets, not general styling.
What comes next
Module 18 covers the box model in full — content, padding, border, and margin, box-sizing, and the margin-collapsing behaviour that catches nearly every engineer off guard at least once.
Module 18 → The Box Model — Margin, Border, Padding, ContentDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.