π¨ 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.
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;
}
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.
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; }
| Pattern | Meaning | Matches |
|---|---|---|
[attr] | Has the attribute | Any element with it |
[attr="val"] | Exact value | type="text" |
[attr~="val"] | One whole word in the value | class="a featured b" |
[attr^="val"] | Starts with | href="https://β¦" |
[attr$="val"] | Ends with | href="doc.pdf" |
[attr*="val"] | Contains substring | href="β¦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:
- Origin & importance β e.g. an author's
!importantbeats a normal author rule. - Specificity β a score based on what the selector is made of.
- 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.
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-boxreset near the top of your CSS
β Don't
- Reach for
!importantto 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
styleattributes 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
!importantfor 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.