Skip to main content

🎨 CSS Syntax and Selectors

If HTML is the skeleton of a web page, CSS β€” Cascading Style Sheets β€” is its skin, clothing, and makeup. HTML says what the content is; CSS decides how it looks. In this lesson you'll learn the grammar of CSS and, more importantly, how to aim your styles precisely at the elements you want to change.

Week 1 · Wednesday: CSS Basics · Lecture 1

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Attach CSS to HTML three different ways and explain why external stylesheets win
  • Read and write a CSS rule, naming every part: selector, property, value, declaration
  • Target elements with element, class, ID, universal, and attribute selectors
  • Combine selectors with descendant, child, and sibling combinators
  • Style element states and sub-parts using pseudo-classes and pseudo-elements
  • Predict which rule wins by calculating specificity and applying the cascade

Estimated Time: 60 minutes

Practice: Style a small article page using at least five selector types, hover states, and pseudo-elements.

In This Lesson

What CSS Is For

You've built pages with HTML β€” headings, paragraphs, lists, forms. Left alone, a browser renders them with plain default styling: black serif-ish text on a white background, blue underlined links. Functional, but nobody would call it designed. CSS is the language that changes that. It lets you set colors, fonts, spacing, borders, and layout, keeping presentation cleanly separated from structure.

That separation is the whole point. The same HTML can look completely different depending on the stylesheet attached to it β€” light mode or dark mode, print or screen, desktop or phone β€” with no change to the markup at all.

graph LR A[HTML
structure] --> P[Web Page] B[CSS
presentation] --> P C[JavaScript
behavior] --> P P --> U[What the user sees]

"Cascading" is in the name for a reason: many rules can target the same element, and CSS has a precise system for deciding which one wins. We'll build up to that β€” but first, how do you attach CSS to a page at all?

Three Ways to Add CSS

Just like JavaScript, CSS can be attached to a page in three places, listed here from "avoid" to "do this."

1. Inline styles (avoid)

<p style="color: blue; font-size: 16px;">This is blue text.</p>

πŸ‘Ž A style attribute applies only to that one element and can't be reused. It also mixes presentation into your markup and wins almost every specificity fight, which makes it hard to override later. Fine for a quick test; wrong for real work.

2. Internal styles

<head>
    <style>
        p {
            color: blue;
            font-size: 16px;
        }
    </style>
</head>

πŸ‘ A <style> block in the <head> styles the whole page. Acceptable for a single, self-contained page or a quick prototype, but those rules can't be shared with other pages.

3. External stylesheet (best)

<head>
    <link rel="stylesheet" href="/styles/main.css">
</head>

πŸ‘πŸ‘ A separate .css file linked with <link> can be reused across every page, is cached by the browser after the first load, and keeps all your styling in one maintainable place. This is what real projects use β€” including the page you're reading right now.

πŸ“– Why separation of concerns matters

Keeping structure (HTML), presentation (CSS), and behavior (JS) in separate files means you can restyle an entire site by editing one stylesheet, hand the design work to a different person than the one writing markup, and reason about each layer on its own. It's the same instinct behind putting JavaScript in an external file.

Anatomy of a Rule

CSS is written as a series of rules. Each rule points at some elements (the selector) and then lists the styles to apply to them (the declaration block).

selector {
    property: value;   /* one declaration */
    property: value;
}

Here's a real one that centers all top-level headings and paints them blue:

h1 {
    color: blue;
    font-size: 24px;
    text-align: center;
}
A CSS rule broken into selector, declaration block, property, and value h1 { color : blue ; } Selector Property Value Declaration block
Every rule is a selector followed by a declaration block in braces. Each line inside is a declaration β€” a property, a colon, a value, and a semicolon.

Comments

CSS has one comment syntax, /* ... */, and it can span multiple lines. There is no single-line // comment in CSS.

/* This is a CSS comment */

/*
   Multi-line comments
   are perfectly fine.
*/

/* TODO: revisit these brand colors */

⚠️ Don't forget the semicolon

Each declaration ends with a semicolon. You can technically omit it on the last declaration in a block, but always including it saves you a broken rule the next time you add a line below it.

Basic Selectors

Selectors are patterns that match HTML elements β€” think of them as search queries for your document. Master these five and you can target almost anything.

graph TD A[Basic Selectors] --> B["Element β€” p, h1, div"] A --> C["Class β€” .classname"] A --> D["ID β€” #idname"] A --> E["Universal β€” *"]

1. Element (type) selector

Matches every element of that tag name.

/* Every <p> on the page */
p {
    color: blue;
}

/* Every <h1> */
h1 {
    font-size: 32px;
}

2. Class selector

Matches any element carrying that class. Classes are the workhorse of CSS: reusable, low-specificity, and you can put the same class on as many elements as you like.

/* Any element with class="highlight" */
.highlight {
    background-color: yellow;
}

.error-message {
    color: red;
    border: 1px solid red;
}

HTML side: <p class="highlight">Highlighted text</p>

3. ID selector

Matches the single element with that id. IDs must be unique on a page, and they carry high specificity β€” which is exactly why you should use them sparingly for styling.

/* The one element with id="site-header" */
#site-header {
    background-color: navy;
    color: white;
}

HTML side: <div id="site-header">…</div>

4. Universal selector

The * matches every element. Its classic use is a reset:

/* Reset spacing and use a saner box model everywhere */
*, *::before, *::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

πŸ’‘ Class vs ID β€” which do I use?

Reach for classes almost always. They're reusable and keep specificity low and predictable. Save IDs for JavaScript hooks and in-page anchor links (href="#section"), not for everyday styling.

Combining Selectors

Combinators let you match elements based on their relationship to other elements β€” a child of, a sibling of, a descendant of. The character between two selectors decides the relationship.

Descendant (space)

/* Any <a> anywhere inside a <nav>, at any depth */
nav a {
    text-decoration: none;
}

Child (>)

/* Only <li> that are DIRECT children of a <ul> */
ul > li {
    list-style-type: square;
}

Adjacent sibling (+)

/* The <p> immediately after an <h1> β€” a lead paragraph */
h1 + p {
    font-size: 18px;
    font-weight: bold;
}

General sibling (~)

/* Every <p> that follows an <h1> under the same parent */
h1 ~ p {
    color: gray;
}

Selector list (,)

A comma is not a combinator β€” it's "or." One rule, applied to several selectors:

/* Apply the same styles to h1, h2, and h3 */
h1, h2, h3 {
    font-family: Arial, sans-serif;
    color: navy;
}

⚠️ Space vs comma β€” a common mix-up

nav a (space) means "an a inside a nav." nav, a (comma) means "every nav and every a on the page." One character, completely different result.

Attribute Selectors

Attribute selectors match on an element's attributes and their values. They're perfect for styling form inputs by type or links by destination β€” without adding classes to your HTML.

/* Has the attribute at all */
[title] { cursor: help; }

/* Exact value */
[type="text"] { border: 1px solid gray; }

/* Value is one word in a space-separated list */
[class~="featured"] { border: 2px solid gold; }

/* Value starts with... β€” great for external links */
[href^="https"] { color: green; }

/* Value ends with... β€” flag PDF downloads */
[href$=".pdf"] { font-weight: bold; }

/* Value contains a substring anywhere */
[href*="example"] { color: blue; }
PatternMeaningMatches
[attr]Has the attributeAny element with it
[attr="val"]Exact valuetype="text"
[attr~="val"]One whole word in the valueclass="a featured b"
[attr^="val"]Starts withhref="https://…"
[attr$="val"]Ends withhref="doc.pdf"
[attr*="val"]Contains substringhref="…example…"

Pseudo-classes & Pseudo-elements

Pseudo-classes β€” styling a state

A pseudo-class (one colon) targets an element in a particular state: being hovered, focused, checked, or in a certain position among its siblings. Think of it as a conditional selector.

/* Link states β€” always write them in this order (LVHA) */
a:link    { color: blue; }     /* not yet visited */
a:visited { color: purple; }   /* already visited */
a:hover   { color: red; }      /* pointer over it */
a:active  { color: orange; }   /* being clicked */

/* Form states */
input:focus    { outline: 2px solid blue; }
input:disabled { opacity: 0.5; }
input:checked  { accent-color: green; }

/* Structural β€” position among siblings */
li:first-child      { font-weight: bold; }
li:last-child       { margin-bottom: 0; }
li:nth-child(odd)   { background-color: #f0f0f0; }  /* zebra striping */

/* Negation */
div:not(.special) { color: gray; }

βœ… The LVHA order

Write link pseudo-classes as Link, Visited, Hover, Active ("LoVe HAte"). Because they share equal specificity, source order breaks ties β€” put them out of order and :hover may never show.

Pseudo-elements β€” styling a sub-part

A pseudo-element (two colons) styles a specific piece of an element, or inserts generated content that isn't in the HTML at all.

/* A drop-cap on the first letter */
p::first-letter {
    font-size: 2em;
    font-weight: bold;
    float: left;
}

/* Insert decorative content before/after β€” content is required */
.quote::before { content: "\201C"; }  /* opening curly quote */
.quote::after  { content: "\201D"; }  /* closing curly quote */

/* Restyle highlighted text and placeholder text */
::selection          { background-color: yellow; color: black; }
input::placeholder   { color: #999; font-style: italic; }

πŸ’‘ One colon or two?

Modern CSS uses one colon for pseudo-classes (:hover) and two for pseudo-elements (::before). The two-colon form was introduced to tell the two apart; browsers still accept the old single-colon :before, but write ::before in new code.

Specificity & the Cascade

When two rules set the same property on the same element, which wins? CSS answers with three tie-breakers, checked in order:

  1. Origin & importance β€” e.g. an author's !important beats a normal author rule.
  2. Specificity β€” a score based on what the selector is made of.
  3. Source order β€” if specificity ties, the rule written last wins.

How specificity is scored

Read specificity as three numbers, (IDs, classes, elements). Count the ID selectors, then class/attribute/pseudo-class selectors, then element/pseudo-element selectors. Higher, compared left to right, wins.

Specificity increases from element selectors up to inline styles Inline style attribute β€” wins over selectors (1,0,0,0) ID selector β€” #content (0,1,0,0) Class / attribute / pseudo-class β€” .text (0,0,1,0) Element / pseudo-element β€” p (0,0,0,1) stronger β†’
Specificity climbs as selectors get more specific. An inline style attribute outranks all of them, and !important outranks even that.
p            { color: blue; }    /* (0,0,0,1) */
.text        { color: red; }     /* (0,0,1,0) β€” beats a bare element */
#content     { color: green; }   /* (0,1,0,0) β€” beats a class */
p.text       { color: orange; }  /* (0,0,1,1) */
#content .text { color: purple; }/* (0,1,1,0) β€” highest here, this wins */

The !important escape hatch

p {
    color: red !important;  /* overrides normal rules regardless of specificity */
}

⚠️ Treat !important as a last resort

It short-circuits the cascade and the only way to beat one !important is another, more specific !important β€” a spiral that makes stylesheets miserable to maintain. Fix the specificity of your selectors instead.

Inheritance

Separate from the cascade: some properties automatically pass from a parent to its descendants. Set font-family, color, or line-height on body and every element inside inherits them unless overridden. Layout properties like margin, padding, and border do not inherit.

body {
    font-family: Arial, sans-serif;  /* inherited by everything */
    color: #333;                      /* inherited */
    line-height: 1.6;                 /* inherited */
    border: 1px solid black;          /* NOT inherited β€” stays on body */
}

.child {
    color: inherit;   /* explicitly take the parent's value */
    all: unset;        /* reset everything back to inherited/initial */
}

Practice & Quiz

πŸ‹οΈ Exercise 1: Zebra-striped, hoverable list

Goal: Given this HTML, write CSS that (a) removes the bullet markers, (b) gives every even row a light gray background, (c) bolds the first row, and (d) turns a row blue while the pointer is over it β€” all without adding a single class or ID.

<ul class="menu">
    <li>Home</li>
    <li>About</li>
    <li>Services</li>
    <li>Contact</li>
</ul>
πŸ’‘ Hint

Use a descendant selector .menu li as your base, then layer on the pseudo-classes :nth-child(even), :first-child, and :hover. Turn off bullets with list-style: none on the <ul>.

βœ… Solution
.menu {
    list-style: none;
    padding: 0;
}
.menu li {
    padding: 8px 12px;
}
.menu li:nth-child(even) {
    background-color: #f0f0f0;
}
.menu li:first-child {
    font-weight: bold;
}
.menu li:hover {
    background-color: #cfe8ff;
    cursor: pointer;
}

πŸ‹οΈ Exercise 2: Predict the winner

Goal: The paragraph <p class="note" id="lead"> is targeted by all three rules below. What color is the text? Work out each selector's specificity before you peek.

p            { color: black; }
.note        { color: green; }
#lead.note   { color: purple; }
βœ… Solution

Purple. The scores are (0,0,0,1), (0,0,1,0), and (0,1,1,0). The last rule has an ID plus a class, the highest score, so it wins regardless of source order.

🎯 Quick Quiz

Question 1: Which way of adding CSS is the best choice for a real multi-page site?

Question 2: What does the selector ul > li match?

Question 3: A .btn rule and a #save rule both set the color on the same button. Which wins, ignoring source order?

Best Practices & Pitfalls

βœ… Do

  • Style with classes by default β€” reusable and predictable
  • Keep selectors short and flat; deep nesting is fragile and slow to reason about
  • Give classes meaningful names that describe purpose (.card__title) not appearance (.big-red)
  • Put CSS in an external stylesheet and link it from the <head>
  • Add a box-sizing: border-box reset near the top of your CSS

❌ Don't

  • Reach for !important to win a fight β€” fix the selector's specificity instead
  • Style with IDs; their high specificity is hard to override later
  • Confuse the descendant space with the selector-list comma
  • Scatter inline style attributes through your HTML

πŸ“– A note on naming: BEM

A popular convention is BEM β€” Block, Element, Modifier β€” written as .card, .card__title, .card--featured. It keeps class names self-documenting and specificity flat, since everything is a single class. You'll see it constantly in professional codebases.

.card { /* the block */ }
.card__title { /* an element inside the block */ }
.card--featured { /* a modified variant of the block */ }

Summary

πŸŽ‰ Key Takeaways

  • Prefer an external stylesheet β€” reusable, cacheable, maintainable
  • A rule is a selector plus a declaration block of property: value; pairs
  • Aim styles with element, class, ID, universal, and attribute selectors, and relate elements with combinators
  • Pseudo-classes (:hover) target states; pseudo-elements (::before) target sub-parts
  • When rules collide, specificity then source order decide β€” reserve !important for emergencies

πŸ“š Additional Resources

πŸš€ What's Next?

You can now aim CSS precisely at any element. The next lesson turns to sizing and spacing those elements: Box Model and Positioning β€” content, padding, border, and margin, plus how position moves elements around the page.

πŸŽ‰ Well done!

Selectors are the vocabulary of CSS. With them in hand, every styling technique from here on is just deciding what to change once you've decided where.