CSS Introduction

CSS (Cascading Style Sheets) is the language that describes how HTML elements should look. HTML gives a page its structure and content — headings, paragraphs, links, lists — while CSS decides the colors, fonts, spacing, sizing, and layout applied to that structure. Without CSS, every web page would look like a plain, unstyled document: black text, blue links, no layout, no personality. With CSS, the same HTML can look like a newspaper, a dashboard, or a mobile app.

This lesson assumes you already know HTML markup. Here we focus purely on the styling language itself: how rules are written, how the browser decides which rule wins when several could apply, and how to avoid the mistakes that trip up almost every beginner.

Overview: How CSS Works

CSS works by attaching declarations (property/value pairs, like color: red;) to selectors that match elements in the HTML document. A group of declarations attached to a selector is called a rule or ruleset. The browser reads your HTML and builds a tree of elements called the DOM (Document Object Model). It then reads every CSS rule that applies — from a stylesheet file, a <style> block, or an inline style attribute — and builds a second tree called the CSSOM (CSS Object Model), which holds the final, computed style for every element. The browser combines the DOM and CSSOM into a render tree, then performs layout (calculating exact size and position for every box) and finally paint (drawing pixels to the screen).

There are three ways to attach CSS to HTML: an external stylesheet (a separate .css file linked with a <link> tag — the standard, recommended approach for real projects), an internal stylesheet (CSS written inside a <style> element in the document <head>), and inline styles (CSS written directly in an element’s style attribute). Inline styles apply only to that one element and are hard to maintain at scale, so they’re best reserved for quick tests or one-off overrides. Every example in this lesson shows the CSS rules themselves — in practice you’d place them in an external stylesheet.

The word “cascading” in the name is the key idea: many rules can match the same element at once (from the browser’s own default stylesheet, your stylesheet, and possibly multiple rules within it), so CSS needs a deterministic system — the cascade — to decide which declaration ultimately wins. That system is built from three ingredients: origin and importance (author styles normally beat browser defaults, and !important flips that), specificity (how precisely a selector targets an element), and source order (when specificity ties, the last rule declared wins). We’ll walk through specificity in detail below.

Syntax

Every CSS rule has the same basic shape: a selector, followed by a declaration block wrapped in curly braces, containing one or more property: value; pairs.

selector {
  property: value;
  property: value;
}
  • selector — identifies which HTML element(s) the rule targets. Common kinds include the type selector (p, h1, matching every element of that tag name), the class selector (.card, matching every element with class="card"), and the ID selector (#site-nav, matching the single element with id="site-nav").
  • property — the aspect of style being set, such as color, font-size, or margin.
  • value — what the property is set to, such as red, 1.5rem, or 16px.
  • the semicolon — terminates each declaration. It’s technically optional after the very last declaration in a block, but omitting it anywhere else breaks the rule, so always include it.
  • the curly braces — open and close the declaration block. A rule is incomplete, and everything after it may misbehave, if a closing brace is missing.

Examples

Example 1: Styling elements by tag name

h1 {
  color: navy;
  font-size: 2rem;
}

p {
  color: #333333;
  line-height: 1.6;
}

Result: Every <h1> on the page turns navy blue and renders at twice the base font size, while every <p> renders in a dark gray (#333333) with generously spaced lines (1.6 times the text’s font size), making paragraphs easier to read.

This is the simplest kind of selector: it matches every element of that tag, everywhere in the document. That makes type selectors great for broad, page-wide defaults, but risky for anything you want to control precisely — if you later add another <p> that should look different, you’ll need a more specific selector to override this one.

Example 2: Styling a reusable component with a class

.card {
  background-color: #f4f4f4;
  border: 1px solid #cccccc;
  border-radius: 8px;
  padding: 16px;
  margin: 24px auto;
  max-width: 320px;
}

.card h2 {
  color: #1a73e8;
  margin-top: 0;
}

Result: Any element with class="card" becomes a light-gray rounded box, 320px wide at most, centered horizontally on the page (via margin: 24px auto), with 16px of inner padding and a thin gray border. Any <h2> nested inside that box renders in blue with no extra space above it.

Classes are the workhorse selector of real-world CSS: unlike type selectors, they only apply where you explicitly add the class in your HTML, and unlike IDs, the same class can be reused on as many elements as you like — ideal for a repeating component like a card, button, or list item.

Example 3: Styling a unique element and reacting to interaction

#site-nav {
  display: flex;
  gap: 20px;
  background-color: #222222;
  padding: 12px 24px;
}

#site-nav a {
  color: white;
  text-decoration: none;
  font-weight: 600;
}

#site-nav a:hover {
  color: #ffcc00;
  text-decoration: underline;
}

Result: The single element with id="site-nav" becomes a dark horizontal bar whose child links are laid out in a row (via Flexbox) with 20px of space between them. Links inside it appear white, bold, and without an underline by default; when the mouse hovers over a link, it turns yellow and gains an underline.

IDs must be unique per page, so an ID selector is appropriate for one-of-a-kind landmarks like a main navigation bar, header, or footer. The :hover pseudo-class here shows that selectors can target a state, not just an element — the styles inside it apply only while the condition (the cursor being over the link) is true.

How It Works Step by Step: The Cascade and Specificity

When more than one rule matches the same element and sets the same property, the browser must pick a winner. It does this in stages:

1. Origin and importance

Browser default styles (the “user agent stylesheet”) are considered first and are normally overridden by any author (your) styles. A declaration marked !important jumps to the top of its origin’s priority, overriding normal author rules regardless of specificity — which is exactly why it should be used sparingly, as it breaks the predictable ordering everything else relies on.

2. Specificity

If importance and origin are equal, the browser calculates a specificity score for each matching selector, as three counted components:

Selector type Weight Example
ID selectors 100 #site-nav
Classes, attribute selectors, pseudo-classes 10 .card, :hover
Type selectors and pseudo-elements 1 p, ::before

These weights combine per selector: #site-nav a:hover scores 100 (one ID) + 1 (one type) + 10 (one pseudo-class) = 111, which beats .card h2 at 10 + 1 = 11. The selector with the higher total wins, regardless of which rule appears first in the file. The universal selector (*) and combinators like descendant space or > add no specificity of their own.

3. Source order

If two selectors tie exactly on specificity, the cascade falls back to source order: whichever rule was declared later (further down the stylesheet, or in a stylesheet linked later) wins. This is why the order of your CSS rules and <link> tags genuinely matters.

4. Layout and paint

Once every element’s final, “computed” style is resolved, the browser calculates the box model for each element (content, padding, border, margin) to work out exact pixel positions and sizes — the layout pass — and then draws the actual pixels in the paint pass. This is why changing a property like width can be more expensive for the browser than changing color: the former can force layout to be recalculated for many elements, while the latter only requires repainting.

Common Mistakes

Mistake 1: Forgetting a semicolon between declarations

.box {
  color: red
  background-color: blue;
}

Without the semicolon after red, the parser tries to read red background-color: blue as a single, malformed value for color. Depending on the browser, this can invalidate the entire declaration, silently dropping the styling you expected. Always terminate every declaration except optionally the very last one in a block:

.box {
  color: red;
  background-color: blue;
}

Mistake 2: Leaving off units on non-zero lengths

.box {
  width: 100;
  margin-top: 20;
}

These declarations are syntactically well-formed — a property followed by a value — but 100 and 20 are unitless numbers, and CSS length values (other than 0) require a unit like px, %, or rem. Browsers treat a value like this as invalid and ignore the whole declaration, so the element keeps whatever width or margin it already had — a confusing silent failure for beginners. Always attach a unit to non-zero lengths:

.box {
  width: 100px;
  margin-top: 20px;
}

Best Practices

  • Keep CSS in an external stylesheet linked from the <head>, rather than inline style attributes, so styles stay reusable and easy to maintain.
  • Prefer classes over IDs for styling; reserve IDs for unique landmarks or JavaScript hooks, since ID-based selectors carry heavy specificity that’s hard to override later.
  • Write selectors as simple as they need to be — a single class beats a long chain like div.container ul li a, which is both fragile and hard to override.
  • Avoid !important except as a last resort; it breaks the normal cascade and makes future overrides painful.
  • Always end declarations with a semicolon and double-check that every opening brace has a matching closing brace.
  • Group related rules logically (resets, layout, components, utilities) so the cascade’s source-order behavior stays predictable as the stylesheet grows.

Practice Exercises

Exercise 1: Write a rule that makes every <h2> on a page dark green, with a font size of 1.75rem and bold weight.

Exercise 2: Create a class called .alert that gives an element a yellow background, 12px of padding, and a 1px solid orange border. Then write a second rule, .alert.alert-error, that overrides the background to light red only when both classes are present on the same element. Which rule wins for an element with both classes, and why, based on specificity?

Exercise 3: You have two rules: nav a { color: blue; } and #main-nav a { color: green; }. If a link is inside an element with id="main-nav" and is also inside a <nav>, calculate the specificity of each selector and state which color the link ends up with.

Summary

  • CSS controls the presentation (colors, fonts, spacing, layout) of HTML, which controls structure and content.
  • A rule pairs a selector with a declaration block of property: value; pairs inside curly braces.
  • The browser builds a DOM from HTML and a CSSOM from CSS, then computes styles, performs layout, and paints pixels.
  • When multiple rules match the same element, the cascade resolves the conflict using origin/importance, then specificity, then source order.
  • Specificity is calculated from ID selectors (100), classes/attributes/pseudo-classes (10), and type selectors/pseudo-elements (1).
  • Common beginner errors — missing semicolons and missing units — can silently invalidate a declaration, so always check both.
  • Favor external stylesheets and class selectors for maintainable, reusable styling.