HTML gives a page structure. CSS gives it personality. With the same HTML, one stylesheet can produce a brutalist poster, a clean dashboard, or a playful storefront.
CSS (Cascading Style Sheets) is a list of rules. Each rule says: find these elements, and change these visual properties. This lesson covers the rules worth knowing first — selectors, the cascade, the box model, units, colors, Flexbox, Grid, and responsive design.
Three ways to add CSS
You can attach styles in three places:
<!-- 1. External stylesheet (preferred) -->
<link rel="stylesheet" href="/styles.css" />
<!-- 2. Inside the document -->
<style>
p { color: navy; }
</style>
<!-- 3. Inline on one element (avoid) -->
<p style="color: navy;">Text</p>Use an external stylesheet. It can be cached by the browser, reused across pages, and keeps structure and presentation separate. Inline styles win every conflict and are painful to override, so reserve them for values JavaScript calculates at runtime.
Anatomy of a rule
/* selector { property: value; } */
.card {
background: white;
padding: 1.5rem;
border-radius: 0.5rem;
}.cardis the selector — which elements this applies to.background,padding,border-radiusare properties.white,1.5rem,0.5remare values.- The whole
property: valuepair is a declaration, and each ends with a semicolon.
Selectors
h1 { } /* every <h1> */
.card { } /* every class="card" */
#site-title { } /* the element with id="site-title" */
nav a { } /* every <a> inside a <nav> */
.card:hover { } /* .card while the mouse is over it */
input:focus { } /* an input while it has keyboard focus */Classes are the workhorse: reusable, combinable, and specific enough. IDs should be unique and are usually reserved for JavaScript hooks or anchor links, because they are hard to override.
The cascade and specificity
When two rules set the same property, the browser decides with two questions:
- Specificity — an ID beats a class, and a class beats an element selector. Inline styles beat all of them.
- Source order — if specificity is equal, the rule written later wins.
p { color: gray; } /* specificity: element */
.note { color: blue; } /* specificity: class */
#warning { color: red; } /* specificity: id */A paragraph with class="note" inside an element with id="warning" will be red.
Avoid !important — it throws specificity out the window and starts an arms race. If you feel you need it, your selector structure is probably wrong.
The box model
Every element is a box with four layers:
┌─────────────────────────────────────────────┐
│ margin │
│ ┌───────────────────────────────────────┐ │
│ │ border │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ padding │ │ │
│ │ │ ┌───────────────────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ └───────────────────────────┘ │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘- Content — the text or image itself.
- Padding — space inside the border, between content and edge. A button’s comfortable click area.
- Border — the visible edge.
- Margin — space outside the border, pushing other elements away.
By default, width describes only the content box, so adding padding silently makes elements wider than you planned. Fix it once, globally:
*,
*::before,
*::after {
box-sizing: border-box;
}Now width: 300px includes padding and border. Every serious stylesheet starts with this.
Units and colors
.title {
font-size: 2rem;
max-width: 60ch;
padding: 1rem 1.5rem;
color: hsl(220 15% 15%);
background: #f5f5f4;
}remis relative to the root font size (16px by default), so2remis 32px. Users who increase their browser font size get a proportional layout.pxis absolute. Fine for borders, risky for typography.%depends on the parent;vwandvhrefer to the viewport.chis the width of the0character — useful for readable line lengths.- Colors can be written as hex (
#f5f5f4),rgb(), orhsl(). All are valid; pick one style and stay consistent.
For colors used more than once, define CSS custom properties:
:root {
--brand: hsl(160 84% 39%);
--text: hsl(220 15% 15%);
}
.button {
background: var(--brand);
color: var(--text);
}Change the variable once and every usage updates.
Layout with Flexbox
Flexbox arranges items along one axis — a row or a column. It is the right tool for navigation bars, toolbars, and rows of cards.
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}display: flexturns the element into a flex container; its children become flex items.justify-contentcontrols the main axis (horizontal in a row).align-itemscontrols the cross axis (vertical in a row).gapadds consistent space between items without margins.
To stack items vertically instead:
.sidebar {
display: flex;
flex-direction: column;
gap: 0.75rem;
}Layout with Grid
Grid arranges items in two dimensions — rows and columns at the same time. It is ideal for card galleries and page layouts.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
}That single line creates as many columns as fit, each at least 220px wide, and wraps automatically. It is one of the most useful CSS snippets you will ever memorize.
Flexbox for one direction, Grid for two. If you find yourself building a layout in the wrong tool, switch tools instead of fighting it.
Responsive design
Responsive means the layout adapts to the screen. Start with the mobile layout — one column, comfortable touch targets — then enhance upward with media queries:
.article {
display: grid;
gap: 1.5rem;
}
@media (min-width: 640px) {
.article {
grid-template-columns: 200px 1fr;
}
}Media queries can test width, height, orientation, and even user preferences like prefers-reduced-motion. Two habits prevent most mobile bugs:
- Never set a fixed width larger than the viewport. Use
max-width. - Give images
max-width: 100%; height: auto;so they scale down instead of overflowing.
Try it
Type this out by hand, then run it in the playground or in your own editor.
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
background: #fafaf9;
padding: 2rem;
}
.card {
max-width: 20rem;
padding: 1.5rem;
background: white;
border: 1px solid #e7e5e4;
border-radius: 0.75rem;
box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
transition: transform 150ms ease, box-shadow 150ms ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgb(0 0 0 / 0.1);
}
</style>
<div class="card">
<h2>Hover me</h2>
<p>The card lifts and the shadow deepens.</p>
</div>Check your understanding
Given this HTML and CSS:
<div id="intro"><p class="note">Hello</p></div>p { color: gray; }
.note { color: blue; }
#intro p { color: pink; }What colour is the paragraph text?
What does box-sizing: border-box change?
Style the recipe page from the previous lesson
Take the recipe.html you built and give it a stylesheet. Create styles.css and link it from the head.
- Start with the universal
border-boxreset and set a font family onbody. - Define
--brand,--text, and--backgroundcustom properties in:rootand use them. - Give the page a centered container:
max-width: 70ch; margin-inline: auto; padding: 1.5rem;. - Make the header a flex container with the nav links spread out using
gapandjustify-content. - Turn the ingredient list into a card with padding, border, and
border-radius. - Display the steps as a grid with
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)). - Add a
:hoverstate to nav links and a visible:focusstyle. - Add one media query that changes something meaningful at
min-width: 640px. - Make every image
max-width: 100%; height: auto;and verify the page at 360px wide in DevTools.
Summary
- Link an external stylesheet; avoid inline styles except for runtime values.
- Specificity: ID beats class beats element; source order breaks ties. Avoid
!important. - Every element is a box: content, padding, border, margin — and you should set
box-sizing: border-boxglobally. - Use
remfor typography,%and viewport units for layout, and custom properties for repeated values. - Flexbox lays out one axis; Grid lays out two.
repeat(auto-fit, minmax(...))handles most card galleries. - Design mobile-first, then add
min-widthmedia queries and never let content overflow.
Your pages now look intentional. Next, JavaScript adds behaviour: variables, logic, functions, and data.