A Plan Before Any CSS
This final lesson puts the whole course into one page: a landing page with a navigation bar, a hero section, a grid of feature cards, a call to action and a footer. Nothing in it is new. What is new is the order of work, because building a page in the right sequence is most of what separates a smooth hour from a frustrating one.
Work mobile-first and outside-in. Mobile-first means the base styles describe the phone layout — a single column — and media queries add complexity as space appears. Outside-in means you settle the page shell before touching any component: the reset, the custom properties, the container width, the base typography. Those decisions constrain everything after them, and changing them once fifteen components exist is painful.
It also helps to decide up front which layout tool each region gets. The navigation bar is a row of things that size themselves to their content, so it is flexbox. The feature grid is a set of equal cells that should reflow, so it is grid with auto-fit. Each card arranges a title, some text and a button in a column with the button pinned to the bottom, so it is a flex column. Making those calls before writing anything stops you rebuilding a section twice.
Finally, write the HTML with the right elements. <header>, <nav>, <main>, <section>, <article> and <footer> cost nothing extra and give the page a structure that assistive technology and search engines can follow. A page built from unmarked <div> elements can be styled to look identical and is considerably less useful.
- Foundation first: reset, custom properties, container, base typography
- Then the header and hero, then the content sections, then the footer
- Nav bar — flexbox, because the items size themselves
- Feature grid — CSS Grid with
auto-fitandminmax() - Each card — a flex column, so the button sits at the bottom whatever the text length
- Media queries added only where the layout visibly stops working
Step 1: The Foundation
Four things go at the top of the stylesheet, and together they prevent a large share of the problems in this course. box-sizing: border-box on everything, so widths mean what you expect. A short reset for the margins browsers add to body, headings and paragraphs. The custom properties — colours, spacing, radius, transition — so every value below is defined once. And base typography on body: font family, a comfortable line-height, and a text colour with real contrast.
Then the container. max-width rather than width, margin-inline: auto to centre it, and horizontal padding so text never touches the screen edge on a phone. Those three declarations are the page shell, and every section will use them.
Notice that the headings use clamp(). That single declaration replaces what would otherwise be a font-size override in every media query, and it scales smoothly rather than jumping at a breakpoint. Because the middle value includes a rem term, it still respects a reader who has increased their browser's default text size.
/* 1. Sizing and reset */
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, p, figure { margin: 0; }
img { max-width: 100%; height: auto; display: block; }
/* 2. Design tokens */
:root {
--brand: #d1039e;
--brand-dark: #7a0060;
--text: #222222;
--muted: #555555;
--surface: #ffffff;
--page: #f4f4f7;
--border: #e3e3ea;
--radius: 12px;
--space: 16px;
--transition: 0.2s ease-out;
}
/* 3. Base typography */
body {
font-family: system-ui, Arial, sans-serif;
line-height: 1.6;
color: var(--text);
background: var(--page);
}
h1 { font-size: clamp(1.9rem, 1.2rem + 3vw, 3.2rem); line-height: 1.15; }
h2 { font-size: clamp(1.4rem, 1.1rem + 1.2vw, 2rem); line-height: 1.25; }
/* 4. The page shell */
.container {
max-width: 1100px;
margin-inline: auto;
padding-inline: 20px;
}
.section { padding-block: clamp(48px, 8vw, 96px); } - Resist the temptation to reset everything with
* { margin: 0; padding: 0; }. It strips the sensible defaults from lists, form controls and buttons as well, and you end up rewriting behaviour the browser already had right. Resetting the handful of elements whose default margins actually get in your way is enough.
Step 2: Header, Hero and the One Breakpoint
The header is a flex row with justify-content: space-between, which pushes the logo to one end and the links to the other with no widths involved. On a narrow screen that row runs out of space, so the base styles stack it as a column and a single min-width query switches it back to a row once there is room. This is the mobile-first pattern in its simplest form: the phone version is the plain one, and the media query adds the arrangement.
The hero is a flex column centred on both axes, with a min-height so it has presence without being fixed to a size. Use min-height rather than height, so that if the text wraps to more lines on a small screen the section grows instead of overflowing.
Two details make the buttons feel finished. Their padding is written in em, so it scales with the button's own font size and a larger variant needs only one declaration. And they carry both a :hover and a :focus-visible style, so a keyboard user gets exactly the same feedback as a mouse user.
The hero background is a gradient over a solid colour, so there is no image to download and nothing to wait for. If you later add a photograph behind it, keep the gradient layered on top — it is what guarantees the white text stays readable whatever the photograph happens to look like in that corner.
/* Header: stacked on phones, a row when there is room */
.site-header {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 16px 20px;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.site-nav { display: flex; gap: 24px; flex-wrap: wrap; }
.site-nav a {
color: var(--muted);
text-decoration: none;
padding: 6px 2px;
border-bottom: 2px solid transparent; /* space reserved, no jump */
transition: color var(--transition), border-color var(--transition);
}
.site-nav a:hover,
.site-nav a:focus-visible { color: var(--brand); border-bottom-color: var(--brand); }
@media (min-width: 700px) {
.site-header { flex-direction: row; justify-content: space-between; }
}
/* Hero */
.hero {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 20px;
min-height: 60vh;
padding: 64px 20px;
background: linear-gradient(135deg, var(--brand), var(--brand-dark));
color: #ffffff;
}
/* Buttons */
.btn {
display: inline-block;
padding: 0.75em 1.75em; /* em, so it scales with font-size */
border: 0;
border-radius: 999px;
font-size: 1rem;
cursor: pointer;
text-decoration: none;
transition: transform var(--transition), box-shadow var(--transition);
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0,0,0,0.18); }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 3px solid #ffffff; outline-offset: 3px; } justify-content: space-betweenseparates logo and links with no widths- Base styles are the stacked phone version; the media query adds the row
min-height, neverheight, on sections whose text can growempadding on buttons scales the whole button from one font-size change- A transparent border reserves the space so hover states never shift the layout
- Every hover style has a matching
:focus-visiblestyle
Step 3: Content, Polish and the Final Checks
The feature section is where Grid earns its place. repeat(auto-fit, minmax(240px, 1fr)) produces three columns on a laptop, two on a tablet and one on a phone without a single media query, and it will keep working if you add a fourth card next month. Each card is then a flex column with justify-content: space-between, so the link sits on the bottom edge whether the description runs to one line or four — which is what makes a row of cards look aligned rather than approximately aligned.
Polish is a short list, and each item is a few lines. Hover and focus states on everything interactive. A prefers-reduced-motion block that cuts the durations for anyone who has asked for less movement. A prefers-color-scheme: dark block that redefines five custom properties — and because every rule in the stylesheet is written in terms of those properties, that is the entire dark theme.
Then check the page rather than admiring it. Drag the window from very narrow to very wide and watch for horizontal scrolling or text that becomes too wide to read comfortably. Tab through the whole page and confirm you can always see where you are. Zoom the browser to 200% and check nothing is cut off. Look at it in dark mode. If you can, open it on a real phone, because that is the only way to find tap targets that are too small.
The demo below is the finished page. Read it as one piece rather than as separate rules — the base layer, then the layout, then the components, in the order this lesson built them. Change the values in :root first: one edit there re-themes the entire page, which is the clearest possible demonstration of why the custom properties went in at step one.
/* The feature grid: no breakpoint needed */
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}
/* Each card: a flex column so the link sits at the bottom */
.feature {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 12px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 24px;
}
/* The whole dark theme, in five lines */
@media (prefers-color-scheme: dark) {
:root {
--surface: #23232b;
--page: #14141a;
--text: #ececf2;
--muted: #a8a8b6;
--border: #3a3a45;
}
}
/* Respect the reduced-motion setting */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
} HTML
<header class="site-header">
<a class="logo" href="#">Priodemy</a>
<nav class="site-nav">
<a href="#features">Features</a>
<a href="#start">Get started</a>
<a href="#">Contact</a>
</nav>
</header>
<section class="hero">
<h1>Learn CSS by building real pages</h1>
<p class="hero__lead">Every layout on this page uses something from the course: flexbox for the header, grid for the cards, custom properties for the colours.</p>
<a class="btn btn--light" href="#start">Start the first lesson</a>
</section>
<main>
<section class="section container" id="features">
<h2>What you will be able to do</h2>
<p class="section__lead">Three columns on a laptop, two on a tablet, one on a phone — with no media query.</p>
<div class="features">
<article class="feature">
<div>
<h3>Lay out a page</h3>
<p>Choose flexbox or grid deliberately instead of guessing, and know why each one fits.</p>
</div>
<a class="feature__link" href="#">Layout lessons</a>
</article>
<article class="feature">
<div>
<h3>Make it responsive</h3>
<p>Write mobile-first CSS and add breakpoints only where the layout actually breaks. This card has more text than the others, so notice that the link below still lines up with its neighbours.</p>
</div>
<a class="feature__link" href="#">Responsive lessons</a>
</article>
<article class="feature">
<div>
<h3>Theme it</h3>
<p>Define colours once as custom properties and change the whole page from one place.</p>
</div>
<a class="feature__link" href="#">Variables lesson</a>
</article>
</div>
</section>
<section class="cta" id="start">
<div class="container cta__inner">
<h2>Ready to build your own?</h2>
<p>Change the values in <code>:root</code> and watch the entire page re-theme itself.</p>
<a class="btn btn--primary" href="#">Open the editor</a>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container">
<p>Built with HTML and CSS only. No frameworks, no JavaScript.</p>
</div>
</footer> CSS
/* ============ 1. BASE ============ */
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, p { margin: 0; }
img { max-width: 100%; height: auto; display: block; }
:root {
--brand: #d1039e;
--brand-dark: #7a0060;
--text: #222222;
--muted: #555555;
--surface: #ffffff;
--page: #f4f4f7;
--border: #e3e3ea;
--radius: 12px;
--transition: 0.2s ease-out;
}
body {
font-family: system-ui, Arial, sans-serif;
line-height: 1.6;
color: var(--text);
background: var(--page);
}
h1 { font-size: clamp(1.8rem, 1.2rem + 2.6vw, 2.9rem); line-height: 1.15; }
h2 { font-size: clamp(1.35rem, 1.1rem + 1vw, 1.9rem); line-height: 1.25; }
h3 { font-size: 1.05rem; margin-bottom: 6px; }
/* ============ 2. LAYOUT ============ */
.container {
max-width: 1000px;
margin-inline: auto;
padding-inline: 20px;
}
.section { padding-block: clamp(40px, 7vw, 80px); }
/* ============ 3. COMPONENTS ============ */
/* Header — stacked first, row when there is room */
.site-header {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 16px 20px;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.logo {
font-weight: 700;
font-size: 1.15rem;
color: var(--brand);
text-decoration: none;
}
.site-nav { display: flex; gap: 22px; flex-wrap: wrap; }
.site-nav a {
color: var(--muted);
text-decoration: none;
padding: 4px 2px;
border-bottom: 2px solid transparent;
transition: color var(--transition), border-color var(--transition);
}
.site-nav a:hover,
.site-nav a:focus-visible {
color: var(--brand);
border-bottom-color: var(--brand);
}
@media (min-width: 700px) {
.site-header { flex-direction: row; justify-content: space-between; }
}
/* Hero */
.hero {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 18px;
min-height: 58vh;
padding: 56px 20px;
background: linear-gradient(135deg, var(--brand), var(--brand-dark));
color: #ffffff;
}
.hero__lead { max-width: 52ch; opacity: 0.92; }
/* Buttons */
.btn {
display: inline-block;
padding: 0.75em 1.75em;
border: 0;
border-radius: 999px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
text-decoration: none;
transition: transform var(--transition), box-shadow var(--transition);
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0,0,0,0.18); }
.btn:active { transform: translateY(0); }
.btn--light { background: #ffffff; color: var(--brand); }
.btn--primary { background: var(--brand); color: #ffffff; }
.btn--light:focus-visible { outline: 3px solid #ffffff; outline-offset: 3px; }
.btn--primary:focus-visible { outline: 3px solid var(--brand-dark); outline-offset: 3px; }
/* Feature grid — reflows with no media query */
.section__lead { color: var(--muted); max-width: 60ch; margin: 8px 0 28px; }
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
}
.feature {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 14px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 22px;
transition: transform var(--transition), box-shadow var(--transition);
}
.feature p { color: var(--muted); font-size: 0.95rem; }
.feature__link {
color: var(--brand);
font-weight: 600;
text-decoration: none;
border-bottom: 2px solid transparent;
align-self: flex-start;
}
.feature__link:hover,
.feature__link:focus-visible { border-bottom-color: var(--brand); }
@media (hover: hover) {
.feature:hover { transform: translateY(-4px); box-shadow: 0 10px 26px rgba(0,0,0,0.10); }
}
/* Call to action */
.cta { background: var(--surface); border-block: 1px solid var(--border); }
.cta__inner {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 14px;
padding-block: clamp(40px, 7vw, 72px);
}
.cta code {
background: var(--page);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
/* Footer */
.site-footer {
padding-block: 28px;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
}
/* ============ 4. PREFERENCES ============ */
@media (prefers-color-scheme: dark) {
:root {
--surface: #23232b;
--page: #14141a;
--text: #ececf2;
--muted: #a8a8b6;
--border: #3a3a45;
}
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
} - Read the finished stylesheet from top to bottom once. Base, then layout, then components, then preference queries — the same order as the architecture lesson. That order is not decoration: it is what makes the later rules able to override the earlier ones without anything having to be made more specific.
Where to Take This Next
You now have the whole toolkit: selectors and the cascade, the box model, flexbox and grid, positioning, responsive design, transitions and animation, custom properties, and a way of organising all of it. The gap between knowing those and being good at CSS is closed by building things, not by reading more.
The most useful next step is to rebuild a page you already use. Pick a site you visit often, open it on your phone, and recreate one screen of it from scratch. You will hit real problems in a real order — a heading that wraps badly, a card grid that will not line up, a sticky header that refuses to stick — and solving those is what turns the list above into something you can use without looking things up.
When you get stuck, the developer tools answer most questions faster than any search. The Styles panel tells you which rules matched and which lost. The box-model diagram tells you where the space is coming from. The grid and flexbox overlays draw the tracks on the page. Getting comfortable with those three is worth more than memorising any property list.
And when a layout is genuinely misbehaving, go back to the four questions this course keeps returning to. What is the box actually being sized as? Which rule is winning, and why? Is this element still in normal flow? And is it inside something — a stacking context, a transformed ancestor, a flex container — that changes the rules for everything within it? Almost every CSS bug you will meet is one of those four wearing a disguise.
- Rebuild a screen from a site you use, on a phone, from scratch
- Add a form — labels, focus states, validation styling — since forms exercise everything at once
- Try the same layout twice, once in flexbox and once in grid, to feel where each fits
- Learn the developer tools properly: Styles panel, box model, grid and flex overlays
- Then move on to JavaScript, which adds behaviour to the interfaces you can now build
- Keep a small file of the snippets you find yourself writing again and again — the border-box reset, the container, the visually-hidden utility, the reduced-motion block, the auto-fit grid. Starting a new project from twenty lines you trust is a much better beginning than a blank file or a framework you have not read.
