Complete HTML Course Module 15: Advanced: Best Practices and Final Project

Final Project: Build a Complete Landing Page in HTML

  • Beginner
  • Lesson 15.6
  • 9 min read
  • 69 of 69 in the course

In short

In this final project you will build a complete landing page in HTML from start to finish. You will combine SEO tags, Open Graph, JSON-LD, accessibility, responsive images, a pricing table, an FAQ and a signup form.

In this final project you will build a complete landing page in HTML from start to finish. You will combine SEO tags, Open Graph, JSON-LD, accessibility, responsive images, a pricing table, an FAQ and a signup form.

What we are building

A landing page is a single page made to do one job, such as getting people to sign up for a product. Ours is for FocusFlow, an imaginary study timer app. The page will have:

  1. A full SEO <head> with Open Graph and JSON-LD.
  2. A skip link and a header with navigation.
  3. A hero section with a responsive <picture>.
  4. A features list.
  5. A pricing table.
  6. An FAQ with <details>.
  7. A signup form with validation.
  8. A footer.

Create a folder called focusflow, and inside it a file called index.html and an images folder. You can use any photos you like for now. We will build the page step by step, and the complete code is at the end.

Step 1: The head with SEO, Open Graph and JSON-LD

Start with the document skeleton and a complete <head>. It has the title and description for Google, a canonical link, Open Graph tags for sharing, a preload for the hero image, and JSON-LD that describes the app as SoftwareApplication.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>FocusFlow – Study Timer App to Beat Procrastination</title>
  <meta name="description" content="FocusFlow is a simple study timer that helps you focus in 25-minute sessions, track your progress and build a daily study habit. Free to start.">
  <link rel="canonical" href="https://focusflow.example/">
  <meta property="og:type" content="website">
  <meta property="og:title" content="FocusFlow – Study Smarter, Not Longer">
  <meta property="og:image" content="https://focusflow.example/images/og-focusflow.jpg">
</head>

This is a shortened version. The full head in the final code also includes og:description, og:url, image sizes, twitter:card, the JSON-LD block and a stylesheet link. Notice that the title puts the main keyword, "study timer app", near the start.

The first thing in <body> is the skip link. Then comes the header with the logo and navigation, which links to sections further down the page using their ids. The hero is the first section in <main>, with the only <h1> on the page and a responsive image.

<a class="skip-link" href="#main">Skip to main content</a>
<header>
  <a href="/" aria-label="FocusFlow home"><img src="images/logo.svg" alt="" width="140" height="36"></a>
  <nav aria-label="Main">
    <ul>
      <li><a href="#features">Features</a></li>
      <li><a href="#pricing">Pricing</a></li>
      <li><a href="#faq">FAQ</a></li>
      <li><a href="#signup">Sign up</a></li>
    </ul>
  </nav>
</header>

The logo link gets its name from aria-label, so the image itself has an empty alt and is not read twice. The hero image (in the final code) uses <picture> with AVIF and WebP, sizes, width and height, and fetchpriority="high", with no lazy loading.

Step 3: Features, pricing and FAQ

Each part of the page is a <section> with an id (for the navigation links) and aria-labelledby pointing to its <h2>. The features are a list of small <article> elements, because each one makes sense on its own. The pricing is a real data table, with a <caption> and scope on header cells, so screen readers can read it cell by cell. The FAQ uses <details> elements that share one name.

<section id="pricing" aria-labelledby="pricing-title">
  <h2 id="pricing-title">Simple pricing</h2>
  <table>
    <caption>Compare FocusFlow plans</caption>
    <thead>
      <tr><th scope="col">Feature</th><th scope="col">Free</th><th scope="col">Pro</th></tr>
    </thead>
    <tbody>
      <tr><th scope="row">Price</th><td>€0</td><td>€4 per month</td></tr>
    </tbody>
  </table>
</section>

The signup form uses everything you learned about forms: labels on every field, the right type for email, required, minlength, autocomplete, and a hint connected with aria-describedby. The browser checks the form before sending it, so users see an error if they leave the email empty or enter fewer than 2 letters for their name.

Note: This form uses action="/signup". On your own computer there is no server to receive the data, so you will see an error page after submitting. That is normal. The validation part still works in the browser.

The footer holds secondary links and the copyright line. The year is in a <small> element, which is meant for legal small print.

The complete landing page code

Here is the full page. Copy it into index.html, add some images with the same names (or change the names), and open it in your browser.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>FocusFlow – Study Timer App to Beat Procrastination</title>
  <meta name="description" content="FocusFlow is a simple study timer that helps you focus in 25-minute sessions, track your progress and build a daily study habit. Free to start.">
  <link rel="canonical" href="https://focusflow.example/">

  <!-- Open Graph and X -->
  <meta property="og:type" content="website">
  <meta property="og:site_name" content="FocusFlow">
  <meta property="og:title" content="FocusFlow – Study Smarter, Not Longer">
  <meta property="og:description" content="A friendly study timer that helps you focus, track progress and build a daily habit.">
  <meta property="og:url" content="https://focusflow.example/">
  <meta property="og:image" content="https://focusflow.example/images/og-focusflow.jpg">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:image:alt" content="The FocusFlow timer on a phone next to an open notebook">
  <meta name="twitter:card" content="summary_large_image">

  <!-- Performance -->
  <link rel="preload" href="images/hero-800.avif" as="image" type="image/avif" fetchpriority="high">
  <link rel="stylesheet" href="css/style.css">
  <script src="js/main.js" defer></script>

  <!-- Structured data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "SoftwareApplication",
    "name": "FocusFlow",
    "applicationCategory": "EducationalApplication",
    "operatingSystem": "Web, iOS, Android",
    "url": "https://focusflow.example/",
    "offers": [
      { "@type": "Offer", "name": "Free", "price": "0", "priceCurrency": "EUR" },
      { "@type": "Offer", "name": "Pro", "price": "4.00", "priceCurrency": "EUR" }
    ]
  }
  </script>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Organization",
    "name": "FocusFlow",
    "url": "https://focusflow.example/",
    "logo": "https://focusflow.example/images/logo.png"
  }
  </script>
</head>
<body>
  <a class="skip-link" href="#main">Skip to main content</a>

  <header>
    <a href="/" aria-label="FocusFlow home">
      <img src="images/logo.svg" alt="" width="140" height="36">
    </a>
    <nav aria-label="Main">
      <ul>
        <li><a href="#features">Features</a></li>
        <li><a href="#pricing">Pricing</a></li>
        <li><a href="#faq">FAQ</a></li>
        <li><a href="#signup">Sign up</a></li>
      </ul>
    </nav>
  </header>

  <main id="main" tabindex="-1">

    <!-- Hero -->
    <section aria-labelledby="hero-title">
      <h1 id="hero-title">The study timer that helps you finally focus</h1>
      <p>FocusFlow breaks your study time into short, calm sessions with small breaks. No stress, no distractions, just steady progress.</p>
      <p><a href="#signup">Start free today</a></p>
      <picture>
        <source type="image/avif" srcset="images/hero-800.avif 800w, images/hero-1600.avif 1600w" sizes="(min-width: 1000px) 50vw, 100vw">
        <source type="image/webp" srcset="images/hero-800.webp 800w, images/hero-1600.webp 1600w" sizes="(min-width: 1000px) 50vw, 100vw">
        <img src="images/hero-800.jpg" srcset="images/hero-800.jpg 800w, images/hero-1600.jpg 1600w" sizes="(min-width: 1000px) 50vw, 100vw" alt="A student studying at a desk with the FocusFlow timer showing 18 minutes left" width="1600" height="1067" fetchpriority="high">
      </picture>
    </section>

    <!-- Features -->
    <section id="features" aria-labelledby="features-title">
      <h2 id="features-title">Why students love FocusFlow</h2>
      <ul>
        <li>
          <article>
            <h3>Focus sessions</h3>
            <p>Study in 25-minute blocks with 5-minute breaks. You can change the times to suit you.</p>
          </article>
        </li>
        <li>
          <article>
            <h3>Progress tracking</h3>
            <p>See how many hours you studied this week, with simple charts and daily streaks.</p>
          </article>
        </li>
        <li>
          <article>
            <h3>Distraction blocker</h3>
            <p>Pause noisy websites and notifications while a session is running.</p>
          </article>
        </li>
        <li>
          <article>
            <h3>Works everywhere</h3>
            <p>Use it in your browser, or on iPhone and Android. Your data syncs automatically.</p>
          </article>
        </li>
      </ul>
      <img src="images/stats-screen.webp" alt="Weekly chart showing 14 hours of study, with Wednesday as the best day" width="1200" height="800" loading="lazy" decoding="async">
    </section>

    <!-- Pricing -->
    <section id="pricing" aria-labelledby="pricing-title">
      <h2 id="pricing-title">Simple pricing</h2>
      <p>Start free. Upgrade only if you want more.</p>
      <table>
        <caption>Compare FocusFlow plans</caption>
        <thead>
          <tr>
            <th scope="col">Feature</th>
            <th scope="col">Free</th>
            <th scope="col">Pro</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th scope="row">Price</th>
            <td>€0</td>
            <td>€4 per month</td>
          </tr>
          <tr>
            <th scope="row">Focus sessions</th>
            <td>Unlimited</td>
            <td>Unlimited</td>
          </tr>
          <tr>
            <th scope="row">Progress history</th>
            <td>7 days</td>
            <td>Forever</td>
          </tr>
          <tr>
            <th scope="row">Distraction blocker</th>
            <td>No</td>
            <td>Yes</td>
          </tr>
          <tr>
            <th scope="row">Devices</th>
            <td>1</td>
            <td>Up to 5</td>
          </tr>
        </tbody>
      </table>
    </section>

    <!-- FAQ -->
    <section id="faq" aria-labelledby="faq-title">
      <h2 id="faq-title">Frequently asked questions</h2>
      <details name="faq">
        <summary>Is FocusFlow really free?</summary>
        <p>Yes. The Free plan has no time limit and no credit card is needed. Pro adds extra features.</p>
      </details>
      <details name="faq">
        <summary>Can I cancel Pro at any time?</summary>
        <p>Yes. Cancel in your account settings with one click. You keep Pro until the end of the month you paid for.</p>
      </details>
      <details name="faq">
        <summary>Does it work offline?</summary>
        <p>The timer works offline. Your progress syncs the next time you are online.</p>
      </details>
      <details name="faq">
        <summary>Is my data private?</summary>
        <p>We never sell your data. You can download or delete everything at any time.</p>
      </details>
    </section>

    <!-- Signup -->
    <section id="signup" aria-labelledby="signup-title">
      <h2 id="signup-title">Create your free account</h2>
      <form action="/signup" method="post">
        <p>
          <label for="name">First name</label>
          <input type="text" id="name" name="name" autocomplete="given-name" required minlength="2" maxlength="50">
        </p>
        <p>
          <label for="email">Email address</label>
          <input type="email" id="email" name="email" autocomplete="email" required aria-describedby="email-hint">
          <small id="email-hint">We'll send one welcome email. No spam.</small>
        </p>
        <p>
          <label for="goal">Daily study goal</label>
          <select id="goal" name="goal">
            <option value="1">1 hour</option>
            <option value="2" selected>2 hours</option>
            <option value="4">4 hours or more</option>
          </select>
        </p>
        <p>
          <input type="checkbox" id="terms" name="terms" required>
          <label for="terms">I agree to the <a href="/terms">terms of service</a></label>
        </p>
        <button type="submit">Start studying free</button>
      </form>
    </section>

  </main>

  <footer>
    <nav aria-label="Footer">
      <ul>
        <li><a href="/about">About us</a></li>
        <li><a href="/privacy">Privacy policy</a></li>
        <li><a href="/terms">Terms of service</a></li>
        <li><a href="mailto:hello@focusflow.example">Contact us</a></li>
      </ul>
    </nav>
    <p><small>© 2026 FocusFlow. All rights reserved.</small></p>
  </footer>
</body>
</html>

Open the page in your browser. It will look plain, with default fonts and everything stacked in one column, because there is no CSS yet. But look at what works already: press Tab and the skip link appears first, the navigation jumps to each section, the FAQ opens and closes, and the form refuses to submit with an empty email or unchecked box.

Tip: Run your page through the W3C validator and Google's Rich Results Test (paste the code). Then test it with the keyboard only, and if you can, with a screen reader. These are the same checks professional developers do before launching.

Practice exercise

Make the project your own. Change FocusFlow into a product you invent, like a recipe app, a gym or a local bakery. Add a "Testimonials" section with three <blockquote> quotes, a third "Team" column in the pricing table, and a <dialog> that opens when the user clicks "Watch demo".

Hint: Each new section needs an id, an <h2>, and an aria-labelledby that matches. Don't forget to add a link to it in the navigation.

Key takeaways

  • A professional page starts with a complete <head>: title, description, canonical, Open Graph and JSON-LD.
  • Accessibility features like a skip link, landmarks and labels are built into the HTML, not added later.
  • Use <picture> and fetchpriority for the hero, and lazy loading for images further down.
  • Real tables, <details> and form validation give you a lot of features with zero JavaScript.
  • Validate and test with the keyboard before you call a page finished.

What's next: learn CSS

Congratulations, you have finished the HTML course! You now know how to build pages that are well structured, accessible, fast and easy for search engines to understand. That is a strong base that many developers skip. Right now your landing page works, but it looks plain. The next step is CSS, the language that controls colours, fonts, spacing and layout. In the CSS course you will style this same kind of page: you will turn the navigation into a horizontal bar, put the features in a grid, make the pricing table look like real pricing cards, design a visible focus style and make everything adapt beautifully to phones. Your clean HTML will make all of that much easier. See you there!