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>&copy; 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-fit and minmax() 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.

Last lessonFinish HTML & CSSMark this lesson complete and pick your next course.