HTML & CSS · Lesson 13 of 13
Project: Build a Responsive Landing Page
HTML and CSS project: build a responsive, accessible landing page with semantic markup, design tokens, flexbox, grid and a mobile-first approach.
- Intermediate
- 25 min read
- 4 objectives
Before this lessonLesson 12: Transitions, Transforms and Animations
What you will learn
- Plan a page as semantic HTML sections before writing CSS
- Set up tokens, a reset and base typography
- Combine flexbox and grid in a mobile-first layout
- Finish with accessibility, performance and SEO checks
Your Progress
0 of 13 lessons 0%
- Lessons0 / 13
- Completed0
- Est. time left~ 4 hours
Create a free account to keep your progress on every device.
Tip: pressing Next marks this lesson complete automatically.
You have learned the pieces: semantic HTML, forms, media, meta tags, selectors, the box model, typography, positioning, flexbox, grid and animation. Real projects are about combining them. In this project you will build a landing page for a fictional product, "stackcone Notes", with a sticky header, a hero, a feature grid, a pricing section, a signup form and a footer.
The workflow matters as much as the code: content and HTML first, then tokens and base styles, then layout, then polish. Work mobile first, because a single column is the simplest layout and larger screens only add to it.
Step 1: Semantic HTML skeleton
Write all the HTML before any CSS. If the page reads sensibly unstyled, it will work for screen readers and search engines.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>stackcone Notes: Markdown Notes for Developers</title>
<meta name="description" content="Fast, searchable Markdown notes with code highlighting and Git sync. Free for individuals, simple pricing for teams.">
<link rel="canonical" href="https://notes.stackcone.com/">
<link rel="stylesheet" href="/css/landing.css">
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a class="logo" href="/">stackcone Notes</a>
<nav aria-label="Primary">
<ul class="nav-list">
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a class="btn btn-small" href="#signup">Get started</a></li>
</ul>
</nav>
</div>
</header>
<main id="main">
<section class="hero">
<div class="container hero-inner">
<div class="hero-copy">
<h1>Notes that think like a developer</h1>
<p class="lead">Markdown, code highlighting and Git sync in one fast app.</p>
<a class="btn" href="#signup">Start free</a>
</div>
<img src="/img/app.webp" alt="stackcone Notes editor showing a Python snippet" width="1200" height="800" fetchpriority="high">
</div>
</section>
<section id="features" class="section">
<div class="container">
<h2>Everything you need</h2>
<ul class="feature-grid" role="list">
<li class="card"><h3>Instant search</h3><p>Find any note in milliseconds.</p></li>
<li class="card"><h3>Code blocks</h3><p>Syntax highlighting for 100+ languages.</p></li>
<li class="card"><h3>Git sync</h3><p>Your notes are plain files in your repo.</p></li>
<li class="card"><h3>Offline first</h3><p>Works on a plane, syncs when you land.</p></li>
</ul>
</div>
</section>
<section id="pricing" class="section section-alt">
<div class="container">
<h2>Simple pricing</h2>
<div class="pricing-grid">
<article class="card plan"><h3>Free</h3><p class="price">$0</p><p>Unlimited personal notes.</p></article>
<article class="card plan plan-featured"><h3>Team</h3><p class="price">$6<span>/user/mo</span></p><p>Shared spaces and SSO.</p></article>
</div>
</div>
</section>
<section id="signup" class="section">
<div class="container narrow">
<h2>Get early access</h2>
<form class="signup" action="/subscribe" method="post">
<label for="email">Work email</label>
<div class="signup-row">
<input id="email" name="email" type="email" autocomplete="email" required>
<button class="btn" type="submit">Join</button>
</div>
</form>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container"><p>© 2026 stackcone</p></div>
</footer>
</body>
</html>Step 2: Tokens, reset and base styles
@layer reset, base, layout, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
img { max-width: 100%; height: auto; display: block; }
button, input { font: inherit; color: inherit; }
}
@layer base {
:root {
color-scheme: light dark;
--color-bg: oklch(99% 0.003 250);
--color-surface: oklch(96% 0.006 250);
--color-text: oklch(24% 0.03 265);
--color-muted: oklch(50% 0.02 260);
--color-accent: oklch(55% 0.2 262);
--color-border: oklch(90% 0.01 250);
--radius: 0.75rem;
--space-s: 0.75rem;
--space-m: 1.5rem;
--space-l: clamp(3rem, 8vw, 6rem);
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: oklch(17% 0.02 265);
--color-surface: oklch(22% 0.02 265);
--color-text: oklch(94% 0.01 250);
--color-muted: oklch(72% 0.02 260);
--color-accent: oklch(72% 0.15 262);
--color-border: oklch(32% 0.02 265);
}
}
body {
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.6;
background: var(--color-bg);
color: var(--color-text);
}
h1, h2, h3 { line-height: 1.15; text-wrap: balance; }
h1 { font-size: clamp(2.25rem, 1.5rem + 3.5vw, 3.75rem); }
h2 { font-size: clamp(1.75rem, 1.3rem + 2vw, 2.5rem); margin-bottom: var(--space-m); }
a { color: var(--color-accent); }
:focus-visible { outline: 3px solid var(--color-accent); outline-offset: 2px; }
}Step 3: Layout with containers, flexbox and grid
Use flexbox for one-dimensional rows (header, form row) and grid for two-dimensional areas (hero, features, pricing). The mobile layout is the default; one media query and auto-fit handle bigger screens.
@layer layout {
.container { width: min(100% - 2rem, 72rem); margin-inline: auto; }
.narrow { max-width: 36rem; }
.section { padding-block: var(--space-l); }
.section-alt { background: var(--color-surface); }
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: color-mix(in oklch, var(--color-bg) 85%, transparent);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--color-border);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
padding-block: var(--space-s);
}
.nav-list { display: flex; align-items: center; gap: var(--space-m); list-style: none; padding: 0; }
.hero { padding-block: var(--space-l); }
.hero-inner { display: grid; gap: var(--space-m); align-items: center; }
@media (min-width: 56rem) {
.hero-inner { grid-template-columns: 1fr 1.2fr; }
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
gap: var(--space-m);
list-style: none;
padding: 0;
}
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: var(--space-m);
}
.signup-row { display: flex; flex-wrap: wrap; gap: var(--space-s); margin-top: 0.25rem; }
.signup-row input { flex: 1 1 14rem; }
}Step 4: Components and polish
@layer components {
.skip-link {
position: absolute;
left: 1rem;
top: -3rem;
padding: 0.5rem 1rem;
background: var(--color-accent);
color: white;
border-radius: var(--radius);
}
.skip-link:focus { top: 1rem; }
.logo { font-weight: 700; text-decoration: none; color: var(--color-text); }
.lead { font-size: 1.25rem; color: var(--color-muted); margin-block: var(--space-s) var(--space-m); }
.btn {
display: inline-block;
padding: 0.75em 1.5em;
border: 0;
border-radius: var(--radius);
background: var(--color-accent);
color: white;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: translate 150ms ease-out, box-shadow 150ms ease-out;
}
.btn:hover { translate: 0 -2px; box-shadow: 0 6px 16px rgb(0 0 0 / 15%); }
.btn-small { padding: 0.4em 1em; }
.card {
padding: var(--space-m);
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
.card h3 { margin-bottom: 0.5rem; }
.price { font-size: 2.5rem; font-weight: 700; }
.price span { font-size: 1rem; color: var(--color-muted); }
.plan-featured { border: 2px solid var(--color-accent); }
.signup input {
padding: 0.75em 1em;
border: 1px solid var(--color-border);
border-radius: var(--radius);
background: var(--color-bg);
}
.site-footer { padding-block: var(--space-m); color: var(--color-muted); text-align: center; }
}
@media (prefers-reduced-motion: reduce) {
.btn { transition: none; }
}Step 5: Test like a professional
- Resize: drag the DevTools responsive view from 320px to 1600px. Nothing should overflow sideways or overlap.
- Keyboard: press Tab from the top. The skip link should appear first, then every link and the form, each with a visible focus ring.
- Dark mode: toggle the OS setting (or DevTools rendering emulation) and check contrast.
- Lighthouse: aim for 90+ in Performance, Accessibility, Best Practices and SEO. Fix any missing alt text, labels or contrast issues it reports.
- Validate: run the HTML through the W3C validator to catch unclosed tags and duplicate IDs.
Recap
- Start with semantic HTML that makes sense unstyled, including a skip link, landmarks and a labelled form.
- Centralise decisions in custom properties and organise CSS with
@layer. - Build mobile first: flexbox for rows, grid with
auto-fitandminmax()for cards, one media query for the hero. - Add polish with small transitions on transform, and respect reduced motion and dark mode.
- Finish with resize, keyboard, Lighthouse and validator checks before shipping.
// Write your solution here
Finished reading? Mark this lesson complete to track your progress.
