Module: CSS
CSS·032·9 MIN READ

032: Responsive Landing Page Capstone

TOPICS COVERED: Responsive Landing Page Capstone

Learning outcomes

By the end, you can plan and build a complete responsive landing page; combine cascade, typography, box model, normal flow, Flexbox, Grid, and media queries; create accessible navigation, calls to action, cards, and forms; test systematically; and explain each layout decision.

Prerequisites and retrieval

Use all 018–031 CSS skills plus the HTML module. Retrieve the decision ladder: semantic HTML, normal flow, fluid constraints, Flexbox for one axis, Grid for two, positioning for overlays, and media queries for justified discrete changes. The capstone is not a race to use every property; it is evidence that you can choose 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 project-planning service. It needs:

Create a new folder with index.html and styles.css, paste the two reference blocks below, and serve it with python -m http.server 8000. Open http://localhost:8000/ so relative assets and form behavior are tested 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.

Content comes before decoration. Write concise real labels rather than lorem ipsum. The following reference build is complete enough to run but intentionally restrained so you can personalize its visual language.

Architecture and mental model

Use one styles.css. Organize from broad to specific: box sizing, element defaults, utilities, components, then media queries. Keep specificity low with classes. Use custom properties for repeated design decisions, not every value. The mobile base is complete; wider queries enhance hero and proof layouts.

For a growing codebase, make ownership explicit: a component class owns its box and internal spacing, a layout class owns relationships between siblings, and a utility is a small documented exception. Avoid selectors that depend on five ancestors or styling by ID. If the team uses cascade layers, a practical order is @layer reset, base, components, utilities, overrides;; keep third-party CSS in a deliberately placed layer rather than winning accidentally. Layers organize precedence, but they do not repair poor source order, inaccessible markup, or an incorrect selector.

Build: complete HTML

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 decorative hero visual is hidden from assistive technology because its text repeats no essential information. The testimonial's figure groups the quotation with a figcaption; the attribution is not part of the quoted words. Every navigation link now points to a useful fragment or application route rather than using # as a placeholder. Form labels and hints remain visible. A real application must return server-side validation and success/error messaging; HTML required is not complete validation.

Build: foundations and components

css
: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 design uses a serif editorial voice instead of a generic app dashboard. Normal flow handles text; Flexbox handles action/navigation rows; Grid handles cards and regional relationships. The hero has min-block-size, not fixed height. Buttons and inputs have practical minimum block size and visible focus.

Intermediate enhancement: content-driven wider layouts

css
@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; }
}

At 48rem, three separate components gain columns because their content has room. No visual reordering occurs. Hover motion is decorative and disappears under reduced-motion preference. Focus does not depend on hover.

Mistakes, debugging, and quality pass

  • 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-index or global overflow: hidden to 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.

Inspect the grid/flex overlays and computed sizes. Use Network to confirm CSS and images load. Use the accessibility tree to inspect landmarks, heading hierarchy, navigation names, form label/description, and decorative content. Tab from the address bar: skip link, navigation, calls to action, email, submit, and footer should follow visual/source order. Trigger native form validation and then plan server errors.

Accessibility and performance audit

Test color contrast, 200% text resize, 400% zoom/reflow, keyboard-only use, focus visibility, reduced motion, forced colors, long translations, and both orientations. At 320px, no page-level horizontal scrollbar should appear. The two-result stats may remain two columns only if readable; otherwise make the narrow base one column.

Replace the decorative visual with optimized CSS as shown or a properly sized image. If adding images, provide intrinsic dimensions, descriptive alt where meaningful, responsive candidates, and lazy loading below the fold. System fonts avoid font downloads. Minify for deployment only after keeping a readable source file. Run Lighthouse or another audit as evidence, not as a substitute for manual keyboard and zoom testing.

Deepening the capstone: build and compare two implementations

A capstone should prove decision-making, not only produce one finished page. Rebuild one section in two valid ways and compare trade-offs.

Version A: Flexbox benefit cards

css
.benefits {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.benefit-card {
  flex: 1 1 18rem;
}

Version B: Grid benefit cards

css
.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?

Document the decision in a short comment or project README rather than keeping both versions.

Capstone enhancement: complete component-state matrix

Every interactive element should be checked in these states where applicable:

css
.button { ... }
.button:hover { ... }
.button:focus-visible { ... }
.button:active { ... }
.button:disabled { ... }

For links:

css
.nav-link[aria-current="page"] { ... }

For form controls:

css
.field input:focus-visible { ... }
.field input:user-invalid { ... }

Do not rely on color alone. Focus, disabled, current, and invalid states should remain perceivable.

Capstone enhancement: deliberate failure cases

Change the content to stress the design.

Very long headline

html
<h1>
  Plan complex cross-functional work without losing the priorities your team agreed on
</h1>

Long email

text
a.very.long.departmental.account@example-international-company.test

Five benefits instead of three

Add two cards and verify the layout does not assume a specific count.

No hero image

Disable image loading. The text should remain readable and the section should 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:

css
/* avoid giant, unused selector chains */
body main .page .content section.cards article.card div.body a.button { ... }

Prefer:

css
.card__action { ... }

Avoid animating layout-heavy properties for decorative motion when a transform can express the same visual effect:

css
/* generally cheaper for movement */
.card:hover {
  transform: translateY(-2px);
}

But performance is not a reason to animate everything. Static interfaces are often best.

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 dominate.

Capstone enhancement: progressive enhancement layer

A base card:

css
.feature-card {
  padding: 1rem;
  border: 1px solid #cbd5e1;
}

Enhance when a feature is available:

css
@supports (container-type: inline-size) {
  .feature-region {
    container-type: inline-size;
  }
}

A later lesson can add actual @container rules. The capstone should remain usable before enhancement.

Professional handoff checklist

Before declaring the capstone complete, verify:

  1. HTML validator does not report structural errors.
  2. CSS has no accidental invalid declarations.
  3. DevTools reports no horizontal page overflow at target sizes.
  4. Every interactive element is keyboard reachable.
  5. Focus indicators are visible.
  6. Color contrast is checked in every state/theme used.
  7. Images have appropriate dimensions and behavior.
  8. Text remains usable with web fonts unavailable.
  9. Motion respects reduced-motion preferences.
  10. Content survives long labels and localization-like expansion.
  11. Source order remains meaningful without CSS.
  12. Breakpoints are documented by content need, not device model.
  13. Repeated design decisions use a coherent token or component strategy.
  14. No !important is present without a documented reason.
  15. The page still communicates its purpose with CSS disabled.

That final test—disable CSS—is powerful. A well-structured page should still expose headings, links, form labels, and content in a meaningful order. 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 adding a query.

Core: Add intrinsic benefit Grid, wider hero/proof/signup Grid enhancement, responsive controls, and full manual test pass. Replace fictional content with a product concept of your own while preserving 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 are the reference solution. Verify:

  • styles.css is linked and no console/network errors remain.
  • Landmarks are header, named navigations, main, sections, and footer.
  • Heading order is one h1, section h2s, and card h3s.
  • 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 !important are used.

Recap and exit questions

The capstone succeeds when its system survives content and user variation, not when it matches one screenshot. Semantic HTML establishes order; low-specificity CSS adds a palette and reading hierarchy; constraints create fluid geometry; Flexbox and Grid express relationships; media queries enhance only when space permits.

  1. Why was Grid chosen for benefits but Flexbox for actions?
  2. Which parts remain usable if CSS fails?
  3. What makes the 48rem query mobile-first?
  4. How does the page protect keyboard, zoom, and reduced-motion users?
  5. What would you measure before adding a custom font or hero image?
  6. How would you assign ownership when a card's spacing is correct alone but wrong inside a grid?
  7. Which design choice would you revise after testing real content?

Official references