032: Responsive Landing Page Capstone
Learning outcomes
By the end of this capstone, you can plan and build a complete responsive landing page. You will combine the cascade, typography, the box model, normal flow, Flexbox, Grid, and media queries; build accessible navigation, calls to action, cards, and forms; test the result methodically; and explain why each layout decision fits the problem.
Prerequisites and retrieval
Bring the CSS skills from 018–031 and the HTML module. Before you start, retrieve the decision ladder: begin with semantic HTML and normal flow, add fluid constraints, use Flexbox for one axis, Grid for two-dimensional relationships, positioning for overlays, and media queries only for changes that genuinely need a discrete breakpoint. The goal is not to demonstrate every property. It is to show that you can select the smallest suitable tool.
Terminology
- Landing page: A focused page built around a clear audience, value proposition, and action (course term).
- Call to action (CTA): A prominent link or button inviting a meaningful next step (course term).
- Design token: A named reusable decision, represented here with custom properties for colors (course term).
- Acceptance criteria: Observable conditions the finished page must satisfy (course term).
- Progressive enhancement: A complete baseline that gains improvements when capabilities or space permit (course term).
- Quality audit: Systematic inspection of semantics, accessibility, responsiveness, performance, and failure states (course term).
- Regression: A previously working behavior broken by a later change (course term).
- Responsive audit: "Verification that content reflows at 320 CSS pixels without two-dimensional scrolling, resizes to 200%, and maintains readability across viewports." — Source: WCAG 2.2: Reflow
- Focus appearance: "Focus indicators must be visible and meet minimum area/contrast." — Source: WCAG 2.2: Focus Appearance
Brief and acceptance criteria
Build a landing page for FocusFlow, a fictional service for planning project work. The page must include:
Create a new folder containing index.html and styles.css. Paste in the two reference blocks below, then serve the folder with python -m http.server 8000. Open http://localhost:8000/ so relative assets and form behavior are exercised in the same context as a real site.
- skip link and semantic header/navigation;
- hero with clear heading, value proposition, primary and secondary actions;
- three benefit cards;
- proof/testimonial section;
- accessible email signup form;
- footer with useful links;
- useful layout from 320px through wide screens and at 200% zoom;
- visible hover and keyboard focus, adequate contrast, no essential motion, and no horizontal page scrolling.
Start with content rather than decoration. Use concise, meaningful labels instead of lorem ipsum. The reference build that follows is complete enough to run, but deliberately restrained so you can give it a visual language of your own.
Architecture and mental model
Keep one styles.css file and organize it from broad rules to specific ones: box sizing, element defaults, utilities, components, and finally media queries. The mobile base should be complete on its own; wider queries enhance the hero and proof layouts when there is room.
As the codebase grows, make ownership explicit. A component class owns its box and internal spacing, a layout class owns the relationship between siblings, and a utility is a small, documented exception. Avoid selectors that require five ancestors and avoid using IDs as styling hooks. If the team uses cascade layers, a practical order is @layer reset, base, components, utilities, overrides;. Put third-party CSS in an intentional layer instead of letting it win by accident. Layers help organize precedence; they cannot fix poor source order, inaccessible markup, or a selector that targets the wrong element.
Build: complete HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="FocusFlow helps small teams plan focused work without busywork.">
<title>FocusFlow | Plan less, finish more</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a class="skip-link" href="#main">Skip to main content</a>
<header class="site-header shell">
<a class="brand" href="/" aria-label="FocusFlow home">Focus<span>Flow</span></a>
<nav aria-label="Primary">
<ul class="nav-list">
<li><a href="#benefits">Benefits</a></li>
<li><a href="#proof">Stories</a></li>
<li><a href="#signup">Join</a></li>
</ul>
</nav>
</header>
<main id="main">
<section class="hero shell">
<div class="hero__content flow">
<p class="eyebrow">Calm planning for small teams</p>
<h1>Plan less. Finish work that matters.</h1>
<p class="hero__lede">FocusFlow turns priorities into a clear weekly path without dashboards full of noise.</p>
<div class="actions">
<a class="button button--primary" href="#signup">Start free</a>
<a class="button button--secondary" href="#benefits">See how it works</a>
</div>
<p class="fine-print">No card required. Cancel any time.</p>
</div>
<div class="hero__visual" aria-hidden="true">
<div class="task-card"><span>Today</span><strong>Publish onboarding guide</strong><small>One clear priority</small></div>
</div>
</section>
<section id="benefits" class="section shell">
<div class="section-heading flow">
<p class="eyebrow">Built for attention</p>
<h2>Less managing, more momentum</h2>
<p>Every feature earns its place by helping your team make and finish a commitment.</p>
</div>
<div class="benefit-grid">
<article class="benefit flow"><span class="benefit__number">01</span><h3>One weekly view</h3><p>See priorities, owners, and blockers without switching boards.</p></article>
<article class="benefit flow"><span class="benefit__number">02</span><h3>Honest capacity</h3><p>Plan around available time before the week becomes overloaded.</p></article>
<article class="benefit flow"><span class="benefit__number">03</span><h3>Useful reflection</h3><p>Close each week with outcomes, lessons, and the next decision.</p></article>
</div>
</section>
<section id="proof" class="proof">
<div class="proof__inner shell">
<figure class="quote flow">
<blockquote>
<p>“Our Monday planning went from an hour of status updates to fifteen minutes of decisions.”</p>
</blockquote>
<figcaption><strong>Maya Chen</strong><br><span>Product lead, Northstar Studio</span></figcaption>
</figure>
<dl class="results">
<div><dt>15 min</dt><dd>weekly planning</dd></div>
<div><dt>32%</dt><dd>fewer carryovers</dd></div>
</dl>
</div>
</section>
<section id="signup" class="signup shell">
<div class="flow">
<p class="eyebrow">Early access</p>
<h2>Make next week clearer</h2>
<p>Join the private beta. We will send setup details, not a daily newsletter.</p>
</div>
<form class="signup-form" action="/signup" method="post">
<div class="field">
<label for="email">Work email</label>
<input id="email" name="email" type="email" autocomplete="email" required aria-describedby="email-hint">
<small id="email-hint">Example: you@company.com</small>
</div>
<button class="button button--primary" type="submit">Request access</button>
</form>
</section>
</main>
<footer class="site-footer">
<div class="shell footer-row">
<p>© 2026 FocusFlow</p>
<nav aria-label="Footer"><a href="/privacy">Privacy</a> <a href="/contact">Contact</a></nav>
</div>
</footer>
</body>
</html>
The hero visual is decorative, and its text repeats no information a user must know, so aria-hidden="true" keeps it out of the assistive-technology tree. The testimonial uses figure to group the quotation and figcaption; the attribution therefore remains separate from the quoted words. Navigation links lead to useful fragments or application routes rather than placeholder # links. Labels and hints stay visible. In a real application, the server must also return validation and success or error messages; HTML required alone is not complete validation.
Build: foundations and components
:root {
--ink: rgb(24 27 35);
--muted: rgb(83 91 108);
--paper: rgb(248 247 243);
--surface: rgb(255 255 255);
--accent: rgb(200 67 38);
--accent-dark: rgb(139 39 20);
--line: rgb(210 207 197);
--focus: rgb(22 101 176);
}
html { box-sizing: border-box; scroll-behavior: smooth; }
*, *::before, *::after { box-sizing: inherit; }
body {
margin: 0;
color: var(--ink);
background: var(--paper);
font-family: Georgia, "Times New Roman", serif;
font-size: 1rem;
line-height: 1.6;
}
img, svg { display: block; max-inline-size: 100%; block-size: auto; }
a { color: inherit; text-underline-offset: .2em; }
a:focus-visible, button:focus-visible, input:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 3px;
}
h1, h2, h3 { margin: 0; line-height: 1.05; text-wrap: balance; }
h1 { max-inline-size: 11ch; font-size: clamp(2.75rem, 2rem + 5vw, 6.5rem); letter-spacing: -.04em; }
h2 { font-size: clamp(2rem, 1.6rem + 2vw, 3.5rem); }
h3 { font-size: 1.4rem; }
p { max-inline-size: 65ch; }
.shell { inline-size: min(100% - 2rem, 72rem); margin-inline: auto; }
.flow > * { margin-block: 0; }
.flow > * + * { margin-block-start: 1rem; }
.skip-link { position: absolute; inset-block-start: 0; inset-inline-start: 1rem; transform: translateY(-150%); z-index: 100; padding: .75rem 1rem; background: white; }
.skip-link:focus { transform: translateY(0); }
.site-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 2rem; padding-block: 1rem; }
.brand { font-family: system-ui, sans-serif; font-size: 1.25rem; font-weight: 850; text-decoration: none; }
.brand span { color: var(--accent); }
.nav-list { display: flex; flex-wrap: wrap; gap: .15rem; margin: 0; padding: 0; list-style: none; }
.nav-list a { display: inline-block; padding: .55rem; font-family: system-ui, sans-serif; font-weight: 700; }
.hero { display: grid; gap: 2rem; align-items: center; min-block-size: 38rem; padding-block: clamp(3rem, 9vw, 8rem); }
.eyebrow { color: var(--accent-dark); font-family: system-ui, sans-serif; font-size: .8rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; }
.hero__lede { font-size: 1.2rem; color: var(--muted); }
.actions { display: flex; flex-wrap: wrap; gap: .75rem; }
.button { display: inline-block; min-block-size: 2.75rem; padding: .7rem 1rem; border: 2px solid var(--accent-dark); border-radius: .2rem; font-family: system-ui, sans-serif; font-weight: 800; text-align: center; text-decoration: none; }
button.button { cursor: pointer; font-size: inherit; }
.button--primary { color: white; background: var(--accent-dark); }
.button--secondary { color: var(--accent-dark); background: transparent; }
.fine-print { color: var(--muted); font-family: system-ui, sans-serif; font-size: .875rem; }
.hero__visual { display: grid; place-items: center; min-block-size: 18rem; background: linear-gradient(145deg, rgb(200 67 38), rgb(104 31 23)); border-radius: 50% 50% 46% 54% / 45% 55% 45% 55%; }
.task-card { inline-size: min(80%, 20rem); padding: 1.25rem; background: var(--surface); box-shadow: 0 1.25rem 3rem rgb(24 27 35 / 25%); transform: rotate(-3deg); }
.task-card span, .task-card small, .task-card strong { display: block; }
.task-card span, .task-card small { font-family: system-ui, sans-serif; color: var(--muted); }
.task-card strong { margin-block: 1rem; font-size: 1.35rem; line-height: 1.2; }
.section { padding-block: clamp(4rem, 9vw, 8rem); }
.section-heading { margin-block-end: 2rem; }
.benefit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); gap: 1rem; }
.benefit { padding: 1.5rem; border-block-start: 3px solid var(--ink); background: var(--surface); }
.benefit__number { font-family: system-ui, sans-serif; color: var(--accent-dark); font-weight: 800; }
.proof { color: white; background: rgb(25 35 34); }
.proof__inner { display: grid; gap: 3rem; padding-block: clamp(4rem, 9vw, 7rem); }
.quote { margin: 0; }
.quote blockquote { margin: 0; }
.quote blockquote p { font-size: clamp(1.6rem, 1.3rem + 1.5vw, 2.5rem); line-height: 1.25; }
.quote figcaption { font-family: system-ui, sans-serif; color: rgb(205 217 214); }
.results { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; margin: 0; }
.results div { border-block-start: 1px solid rgb(255 255 255 / 35%); padding-block-start: 1rem; }
.results dt { font-size: 2rem; font-weight: 700; }
.results dd { margin: 0; color: rgb(205 217 214); }
.signup { display: grid; gap: 2rem; padding-block: clamp(4rem, 9vw, 7rem); }
.signup-form { display: grid; gap: 1rem; align-items: end; }
.field { display: grid; gap: .35rem; }
.field label { font-family: system-ui, sans-serif; font-weight: 800; }
.field input { min-inline-size: 0; min-block-size: 2.75rem; padding: .65rem .75rem; border: 2px solid var(--muted); background: white; font: inherit; }
.field small { color: var(--muted); }
.site-footer { border-block-start: 1px solid var(--line); font-family: system-ui, sans-serif; }
.footer-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; padding-block: 1.5rem; }
.footer-row p { margin: 0; }
.footer-row a + a { margin-inline-start: 1rem; }
The visual language is editorial: a serif reading face replaces the usual app-dashboard treatment. Normal flow handles text, Flexbox handles the action and navigation rows, and Grid handles cards and larger regional relationships. The hero uses min-block-size rather than a fixed height, so content can grow. Buttons and the input have a practical minimum block size, and the focus rule makes keyboard position visible.
Intermediate enhancement: content-driven wider layouts
@media (min-width: 48rem) {
.hero { grid-template-columns: minmax(0, 1.15fr) minmax(16rem, .85fr); }
.proof__inner { grid-template-columns: minmax(0, 2fr) minmax(14rem, 1fr); align-items: end; }
.signup { grid-template-columns: minmax(0, 1fr) minmax(20rem, 1fr); align-items: end; }
.signup-form { grid-template-columns: minmax(0, 1fr) auto; }
}
@media (hover: hover) and (pointer: fine) {
.button--primary:hover { background: var(--accent); border-color: var(--accent); }
.benefit { transition: transform 180ms ease, box-shadow 180ms ease; }
.benefit:hover { transform: translateY(-.2rem); box-shadow: 0 1rem 2rem rgb(24 27 35 / 10%); }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.benefit { transition: none; }
}
Once the viewport reaches 48rem, three independent components get columns because their content has space to support them. The DOM does not reorder visually. Hover movement is optional decoration and is removed for users who request reduced motion; focus remains available without hover.
Mistakes, debugging, and quality pass
Common failures in this kind of capstone are:
- Starting decoration before complete content and semantic structure.
- Using heading levels for size instead of hierarchy.
- Fixing hero/card heights and clipping zoomed text.
- Adding a hamburger icon without button semantics, expanded state, focus handling, and functional scripting.
- Using placeholder-only form labels.
- Making the decorative task card readable only visually despite containing essential claims.
- Copying a huge
z-indexor globaloverflow: hiddento mask layout bugs. - Testing only the chosen 48rem breakpoint.
- Shipping fake links or a form endpoint as production behavior without clearly replacing them.
- Treating a cascade layer or utility as a universal escape hatch: identify the component owner and remove the smallest conflicting rule.
Debug from the layout evidence rather than guessing. Inspect grid and flex overlays and compare the computed sizes. Use Network to confirm that CSS and images load. In the accessibility tree, check landmarks, heading hierarchy, navigation names, the form label and description, and the decorative content. Starting at the address bar, tab through the page: the skip link, navigation, calls to action, email field, submit button, and footer should follow visual and source order. Trigger native form validation, then make a plan for server-returned errors.
Accessibility and performance audit
Test contrast, 200% text resizing, 400% zoom and reflow, keyboard-only use, focus visibility, reduced motion, forced colors, long translations, and both orientations. At 320px, the page itself should not acquire a horizontal scrollbar. The two statistics may stay side by side only while they remain readable; if they do not, make the narrow base one column.
The decorative visual can remain optimized CSS as shown, or become a properly sized image. If you add images, provide intrinsic dimensions, meaningful descriptive alt text, responsive candidates, and lazy loading below the fold. System fonts avoid font downloads. Minify for deployment only after keeping a readable source file. Use Lighthouse or another audit as evidence, not as a replacement for manual keyboard and zoom testing.
Deepening the capstone: build and compare two implementations
A capstone should demonstrate decisions, not merely produce a screenshot-perfect page. Rebuild one section in two valid ways, then compare what each layout makes easier or harder.
Version A: Flexbox benefit cards
.benefits {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.benefit-card {
flex: 1 1 18rem;
}
Version B: Grid benefit cards
.benefits {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
}
Compare:
- Do rows need shared column alignment?
- Do individual lines need independent distribution?
- Which version is easier to explain?
- Which fails more gracefully with five cards instead of three?
- Which keeps long content readable?
Record the decision in a short comment or project README, and keep only the selected version in the implementation.
Capstone enhancement: complete component-state matrix
Check every interactive component in each applicable state, not only its default appearance:
.button { ... }
.button:hover { ... }
.button:focus-visible { ... }
.button:active { ... }
.button:disabled { ... }
For links:
.nav-link[aria-current="page"] { ... }
For form controls:
.field input:focus-visible { ... }
.field input:user-invalid { ... }
Color by itself is not enough to communicate state. Focus, disabled, current, and invalid states all need a perceivable treatment.
Capstone enhancement: deliberate failure cases
Stress the layout by replacing the comfortable sample content with difficult content. A responsive design is only trustworthy after it survives content it did not expect.
Very long headline
<h1>
Plan complex cross-functional work without losing the priorities your team agreed on
</h1>
Long email
a.very.long.departmental.account@example-international-company.test
Five benefits instead of three
Add two cards and verify that the layout does not depend on a particular card count.
No hero image
Disable image loading. The text must stay readable, and the section must not collapse unexpectedly.
200% zoom
At 200% zoom:
- navigation may wrap;
- CTA row may become a column/wrapped row;
- no content should be covered by sticky/fixed UI;
- form labels and errors should remain visible.
Capstone enhancement: performance-oriented CSS review
Look for selectors that make the browser and future maintainers traverse an unnecessarily specific chain:
/* avoid giant, unused selector chains */
body main .page .content section.cards article.card div.body a.button { ... }
Prefer an ownership-based class:
.card__action { ... }
For decorative movement, avoid animating layout-heavy properties when a transform expresses the same visual change:
/* generally cheaper for movement */
.card:hover {
transform: translateY(-2px);
}
That does not mean every element needs animation. A static interface is often the better result.
Review font requests, background images, oversized assets, and repeated rules. CSS file size is only one part of rendering cost; expensive paint effects and large image resources can matter more.
Capstone enhancement: progressive enhancement layer
Start with a card that works without any enhancement:
.feature-card {
padding: 1rem;
border: 1px solid #cbd5e1;
}
Then add a layer only when the browser supports the capability:
@supports (container-type: inline-size) {
.feature-region {
container-type: inline-size;
}
}
A later lesson can introduce actual @container rules. This capstone should remain usable before that enhancement is available.
Professional handoff checklist
Before you call the capstone complete, verify:
- HTML validator does not report structural errors.
- CSS has no accidental invalid declarations.
- DevTools reports no horizontal page overflow at target sizes.
- Every interactive element is keyboard reachable.
- Focus indicators are visible.
- Color contrast is checked in every state/theme used.
- Images have appropriate dimensions and behavior.
- Text remains usable with web fonts unavailable.
- Motion respects reduced-motion preferences.
- Content survives long labels and localization-like expansion.
- Source order remains meaningful without CSS.
- Breakpoints are documented by content need, not device model.
- Repeated design decisions use a coherent token or component strategy.
- No
!importantis present without a documented reason. - The page still communicates its purpose with CSS disabled.
The last check is especially revealing. Disable CSS and inspect the document as a sequence of headings, links, form labels, and content. A sound structure should still communicate the page's purpose. CSS improves presentation; it should not be the only thing making the document understandable.
Tiered capstone exercises
Foundation: Implement semantic HTML, base color/type, shell, header, hero, benefits, signup, footer, and visible focus. It must work at 320px before you add a query.
Core: Add intrinsic benefit Grid, wider hero/proof/signup Grid enhancement, responsive controls, and a full manual test pass. Replace the fictional content with a product concept of your own while preserving the acceptance criteria.
Stretch: Add optimized responsive content imagery or a print stylesheet. Document three design decisions, three accessibility checks, and one measured performance improvement. Do not add JavaScript unless the interaction genuinely requires it.
The complete HTML and CSS above form the reference solution. Verify:
styles.cssis linked and no console/network errors remain.- Landmarks are header, named navigations, main, sections, and footer.
- Heading order is one
h1, sectionh2s, and cardh3s. - Every interactive element is reachable in logical order with visible focus.
- Email has a persistent label, hint, correct type, autocomplete, and required state.
- Base layout has no query dependency and no horizontal page scrolling at 320px.
- At 48rem the hero, proof, and signup gain tracks without DOM reordering.
- At 200% text remains visible; at 400% content reflows.
- Normal text meets 4.5:1 contrast and meaningful UI boundaries meet applicable contrast.
- Reduced-motion preference removes decorative movement and smooth scrolling.
- No fixed text heights, layout floats, inline styles, IDs for CSS specificity, or
!importantare used.
Recap and exit questions
The capstone is successful when its system survives variation in content and users, not when it matches one screenshot. Semantic HTML establishes order; low-specificity CSS adds palette and reading hierarchy; constraints create fluid geometry; Flexbox and Grid express relationships; and media queries enhance only when space permits.
- Why was Grid chosen for benefits but Flexbox for actions?
- Which parts remain usable if CSS fails?
- What makes the 48rem query mobile-first?
- How does the page protect keyboard, zoom, and reduced-motion users?
- What would you measure before adding a custom font or hero image?
- How would you assign ownership when a card's spacing is correct alone but wrong inside a grid?
- Which design choice would you revise after testing real content?
