Skip to main content

๐Ÿ  Lesson 17: Building the Homepage

You've planned your site โ€” purpose, sitemap, wireframes, style guide, folder structure. Now it's time to build. In this lesson, you'll create a complete homepage from scratch: a navigation bar that works on every page, a hero section that greets visitors, feature cards that guide them deeper into your site, and a footer that ties it all together. Every component you build here becomes a reusable pattern for the rest of your pages.

๐ŸŽฏ Learning Objectives

By the end of this lesson, you will be able to:

  • Set up a homepage with proper HTML boilerplate and linked stylesheets
  • Build a responsive navigation bar with a logo, links, and mobile hamburger menu
  • Create a hero section with a heading, tagline, and call-to-action button
  • Build a row of feature cards using Flexbox
  • Construct a reusable footer with links and copyright info
  • Apply your style guide colors, fonts, and spacing with CSS custom properties
  • Make the entire page responsive from desktop to mobile

Estimated Time: 60โ€“90 minutes

Prerequisites: Lesson 16 (Planning Your Site) โ€” you should have a project folder set up with a css/style.css file containing your style guide custom properties.

๐Ÿ“‘ In This Lesson

The Homepage Blueprint

Before writing a single line of code, let's revisit the wireframe from Lesson 16. Your homepage has four major sections stacked vertically:

graph TD A["๐Ÿงญ Navigation Bar
Logo + Links + Hamburger"] B["๐ŸŽฌ Hero Section
Welcome heading + tagline + CTA button"] C["๐Ÿƒ Feature Cards
3 cards: About ยท Recipes ยท Contact"] D["โญ Featured Recipe
Image + title + description + link"] E["๐Ÿ“‹ Footer
Copyright + links"] A --> B --> C --> D --> E style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style C fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#fdf2f8,stroke:#ec4899,stroke-width:2px,color:#1e293b style E fill:#f1f5f9,stroke:#64748b,stroke-width:2px,color:#1e293b

We'll build each section one at a time โ€” writing the HTML first, then styling it immediately so you can see progress with every save. This "build and style as you go" approach keeps you motivated and makes debugging much easier than writing all the HTML first and all the CSS later.

๐Ÿ’ก Work in Small Cycles

Professional developers work in tight feedback loops: write a small chunk of code, save, check the browser, adjust. If you write 200 lines before checking, you'll have 200 lines to debug. Write 10, check, adjust โ€” repeat.

HTML Boilerplate & Page Structure

Open your project's index.html file (the one you created in Lesson 16). If it's still empty, start with this boilerplate:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home - Tasty Bites</title>
    <link rel="stylesheet" href="css/style.css">
    <!-- Google Font (optional โ€” remove if using system fonts) -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
          rel="stylesheet">
</head>
<body>

    <!-- Navigation -->
    <header class="site-header">
        <nav></nav>
    </header>

    <!-- Hero -->
    <section class="hero"></section>

    <!-- Main Content -->
    <main>
        <!-- Feature Cards -->
        <section class="features"></section>

        <!-- Featured Recipe -->
        <section class="featured-recipe"></section>
    </main>

    <!-- Footer -->
    <footer class="site-footer"></footer>

    <script src="js/script.js"></script>
</body>
</html>

What's Happening Here

  • <header class="site-header"> โ€” wraps the navigation. We use <header> because the nav is the page's top-level introductory content.
  • <section class="hero"> โ€” the big welcome area. It's outside <main> because it's a page-level landmark, not the primary article content.
  • <main> โ€” contains the primary content: feature cards and featured recipe.
  • <footer class="site-footer"> โ€” closing info shared across all pages.

Save the file and open it with Live Server (Ctrl+Shift+P โ†’ "Live Server: Open"). You should see a blank white page โ€” that's correct. We'll fill in each section next.

Creating the Hero Section

The hero section is the first thing visitors see below the nav. Its job is to communicate three things instantly: what this site is, who it's for, and what to do next.

Step 1: The HTML

Replace the empty hero section:

<section class="hero">
    <div class="container">
        <h1>Simple Recipes,<br>Made with Love</h1>
        <p class="hero-tagline">
            A collection of my favorite recipes โ€” easy enough for
            weeknights, impressive enough for guests.
        </p>
        <a href="recipe.html" class="btn btn-primary">Browse Recipes</a>
    </div>
</section>

Why an <a> Instead of a <button>?

The call-to-action (CTA) takes visitors to another page, so it's a link styled as a button. Use <a> for navigation and <button> for actions (form submit, toggle, open modal). Styling them to look the same is fine โ€” the semantic difference matters for accessibility and SEO.

Step 2: The CSS

/* ===========================
   Hero Section
   =========================== */
.hero {
    background-color: var(--color-bg-alt);
    padding: var(--space-2xl) 0;
    text-align: center;
}

.hero h1 {
    font-size: clamp(2rem, 5vw, 3rem);
    line-height: 1.2;
    margin-bottom: var(--space-md);
    color: var(--color-text);
}

.hero-tagline {
    font-size: clamp(1.05rem, 2vw, 1.25rem);
    color: var(--color-text-light);
    max-width: 600px;
    margin: 0 auto var(--space-xl);
    line-height: 1.6;
}

/* ===========================
   Buttons (reusable)
   =========================== */
.btn {
    display: inline-block;
    padding: var(--space-sm) var(--space-xl);
    font-size: 1rem;
    font-weight: 600;
    text-decoration: none;
    border-radius: var(--border-radius);
    border: 2px solid transparent;
    cursor: pointer;
    transition: background-color var(--transition-fast),
                color var(--transition-fast),
                border-color var(--transition-fast);
}

.btn-primary {
    background-color: var(--color-primary);
    color: #ffffff;
}

.btn-primary:hover {
    background-color: var(--color-primary-light);
    color: #ffffff;
    text-decoration: none;
}

.btn-outline {
    background-color: transparent;
    color: var(--color-primary);
    border-color: var(--color-primary);
}

.btn-outline:hover {
    background-color: var(--color-primary);
    color: #ffffff;
    text-decoration: none;
}

Check the browser. You should see a centered hero section with a large heading, a muted tagline, and a blue call-to-action button. The clamp() values make the text scale smoothly between mobile and desktop sizes โ€” no media query needed for font sizes.

๐Ÿ’ก The Power of clamp()

clamp(min, preferred, max) sets a value that scales with the viewport but never goes below the minimum or above the maximum. It's the modern CSS way to handle responsive typography without media queries. You learned about it in Lesson 15 โ€” now you're using it for real.

Feature Cards with Flexbox

Feature cards are a common homepage pattern โ€” a row of 2โ€“4 boxes that summarize what visitors will find on your site and link them there. We'll build three cards: About, Recipes, and Contact.

Step 1: The HTML

Replace the empty features section inside <main>:

<section class="features">
    <div class="container">
        <h2>What You'll Find Here</h2>
        <div class="card-grid">

            <article class="card">
                <div class="card-icon">๐Ÿ‘‹</div>
                <h3>About Me</h3>
                <p>Learn about the person behind the recipes โ€”
                   my cooking journey, favorite flavors, and kitchen
                   philosophy.</p>
                <a href="about.html" class="btn btn-outline">Read More</a>
            </article>

            <article class="card">
                <div class="card-icon">๐Ÿณ</div>
                <h3>Recipes</h3>
                <p>Browse tried-and-true recipes with clear
                   ingredients, step-by-step instructions, and tips
                   from real experience.</p>
                <a href="recipe.html" class="btn btn-outline">Explore</a>
            </article>

            <article class="card">
                <div class="card-icon">โœ‰๏ธ</div>
                <h3>Get in Touch</h3>
                <p>Have a question, a suggestion, or just want
                   to say hello? Drop me a message โ€” I'd love to
                   hear from you.</p>
                <a href="contact.html" class="btn btn-outline">Contact</a>
            </article>

        </div>
    </div>
</section>

Breaking It Down

  • <article class="card"> โ€” each card is a self-contained piece of content. <article> is semantically appropriate because each card makes sense on its own.
  • <div class="card-grid"> โ€” a wrapper for the Flexbox layout. This isn't semantic content, so a plain <div> is correct.
  • <div class="card-icon"> โ€” emoji icons add personality. You could replace these with SVG icons or images later.

Step 2: The CSS

/* ===========================
   Features Section
   =========================== */
.features {
    padding: var(--space-2xl) 0;
}

.features h2 {
    text-align: center;
    margin-bottom: var(--space-xl);
}

/* ===========================
   Card Grid (Flexbox)
   =========================== */
.card-grid {
    display: flex;
    gap: var(--space-xl);
    flex-wrap: wrap;
    justify-content: center;
}

.card {
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--border-radius-lg);
    padding: var(--space-xl);
    text-align: center;
    flex: 1 1 280px;         /* grow, shrink, min 280px */
    max-width: 360px;
    transition: box-shadow var(--transition-base),
                transform var(--transition-base);
}

.card:hover {
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
    transform: translateY(-2px);
}

.card-icon {
    font-size: 2.5rem;
    margin-bottom: var(--space-md);
}

.card h3 {
    margin-bottom: var(--space-sm);
}

.card p {
    color: var(--color-text-light);
    margin-bottom: var(--space-lg);
    line-height: 1.6;
}

.card .btn {
    margin-top: auto;
}

How the Flexbox Layout Works

graph LR subgraph "flex: 1 1 280px" A["Card 1
min 280px
grows to fill"] B["Card 2
min 280px
grows to fill"] C["Card 3
min 280px
grows to fill"] end style A fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style B fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style C fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b

The magic is in flex: 1 1 280px:

  • 1 (flex-grow) โ€” cards grow to fill available space equally
  • 1 (flex-shrink) โ€” cards can shrink if needed
  • 280px (flex-basis) โ€” each card wants to be at least 280px wide

Combined with flex-wrap: wrap, this means: on wide screens, all three cards sit in one row. On narrow screens (where three 280px cards won't fit), they wrap to two cards + one, or stack to a single column โ€” with zero media queries.

The max-width: 360px prevents cards from stretching too wide on very large screens.

Making It Responsive

Thanks to Flexbox's flex-wrap and clamp() typography, most of the page is already responsive. But we still need media queries for two things: showing the hamburger menu on mobile and fine-tuning spacing.

Mobile Styles

Add this at the bottom of your css/style.css:

/* ===========================
   Responsive โ€” Mobile (768px and below)
   =========================== */
@media (max-width: 768px) {

    /* Show hamburger, hide nav links */
    .nav-toggle {
        display: block;
    }

    .nav-menu {
        display: none;
        flex-direction: column;
        position: absolute;
        top: 64px;
        left: 0;
        right: 0;
        background-color: var(--color-bg);
        border-bottom: 1px solid var(--color-border);
        padding: var(--space-md);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }

    .nav-menu.open {
        display: flex;
    }

    .nav-link {
        padding: var(--space-md);
        border-radius: var(--border-radius-sm);
    }

    .nav-link:hover {
        background-color: var(--color-bg-alt);
    }

    /* Hero spacing */
    .hero {
        padding: var(--space-xl) 0;
    }

    /* Featured recipe stacks vertically automatically
       via flex-wrap โ€” just adjust spacing */
    .featured-content {
        text-align: center;
    }

    /* Footer stacks */
    .footer-content {
        flex-direction: column;
        text-align: center;
    }
}

How the Mobile Nav Works

graph LR A["Screen โ‰ค 768px"] --> B["Hamburger โ˜ฐ
appears"] B --> C{"User taps โ˜ฐ"} C -->|"JS adds .open"| D["Nav menu
slides down"] C -->|"Tap again"| E["Nav menu
hides"] style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style E fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b

On screens 768px and narrower, the nav links are hidden (display: none) and the hamburger button appears. When the user taps the hamburger, JavaScript toggles the open class, which sets the menu to display: flex in a vertical column. We'll add that JavaScript next.

Adding the Mobile Menu Toggle

The CSS hides and shows the menu โ€” but we need JavaScript to toggle the open class when the hamburger is clicked. This is your first taste of adding interactivity.

The JavaScript

Open (or create) js/script.js and add:

<script>
// Mobile navigation toggle
const navToggle = document.querySelector('.nav-toggle');
const navMenu = document.querySelector('.nav-menu');

if (navToggle && navMenu) {
    navToggle.addEventListener('click', () => {
        const isOpen = navMenu.classList.toggle('open');
        navToggle.setAttribute('aria-expanded', isOpen);
        navToggle.textContent = isOpen ? 'โœ•' : 'โ˜ฐ';
    });

    // Close menu when a link is clicked
    navMenu.querySelectorAll('.nav-link').forEach(link => {
        link.addEventListener('click', () => {
            navMenu.classList.remove('open');
            navToggle.setAttribute('aria-expanded', 'false');
            navToggle.textContent = 'โ˜ฐ';
        });
    });
}
</script>

Since this goes in your js/script.js file (not inside <script> tags), just use the JavaScript content without the surrounding HTML tags:

// Mobile navigation toggle
const navToggle = document.querySelector('.nav-toggle');
const navMenu = document.querySelector('.nav-menu');

if (navToggle && navMenu) {
    navToggle.addEventListener('click', () => {
        const isOpen = navMenu.classList.toggle('open');
        navToggle.setAttribute('aria-expanded', isOpen);
        navToggle.textContent = isOpen ? 'โœ•' : 'โ˜ฐ';
    });

    // Close menu when a link is clicked
    navMenu.querySelectorAll('.nav-link').forEach(link => {
        link.addEventListener('click', () => {
            navMenu.classList.remove('open');
            navToggle.setAttribute('aria-expanded', 'false');
            navToggle.textContent = 'โ˜ฐ';
        });
    });
}

What This Does

  1. Finds the toggle button and menu using querySelector
  2. Listens for clicks on the hamburger button
  3. Toggles the open class โ€” classList.toggle() adds the class if it's missing, removes it if it's there, and returns true or false
  4. Updates aria-expanded so screen readers know the menu state
  5. Swaps the icon โ€” โ˜ฐ when closed, โœ• when open
  6. Closes the menu when any nav link is clicked (useful for same-page links)

๐Ÿ’ก Why the if Guard?

The if (navToggle && navMenu) check prevents errors if the script runs on a page that doesn't have these elements. It's a defensive programming habit โ€” always check that an element exists before attaching event listeners to it.

Test it: resize your browser to a narrow width (or use DevTools responsive mode, Ctrl+Shift+M). The hamburger should appear, and clicking it should reveal and hide the nav links.

Hands-on Exercise

๐Ÿ‹๏ธ Exercise: Build Your Homepage

Objective: Build a complete, responsive homepage with all five components.

Requirements Checklist

  1. Navigation bar โ€” logo, 4 page links, hamburger toggle on mobile
  2. Hero section โ€” heading, tagline, CTA button
  3. Feature cards โ€” 3 cards in a Flexbox row that wraps on small screens
  4. Featured content section โ€” side-by-side image + text layout
  5. Footer โ€” site info, secondary nav, copyright
  6. Responsive โ€” test at 1200px, 768px, and 375px widths
  7. Custom properties โ€” all colors, fonts, and spacing use your style guide variables

Stretch Goals (Optional)

  • Add a background image or gradient to the hero section
  • Add hover animations to the cards (scale up, shadow, color change)
  • Add a second featured recipe with the image on the opposite side
  • Add social media icons to the footer (use Unicode characters or emoji for now)
๐Ÿ’ก Hint โ€” Hero Background Gradient
/* Subtle gradient hero background */
.hero {
    background: linear-gradient(
        135deg,
        var(--color-bg-alt) 0%,
        #dbeafe 100%    /* light blue tint */
    );
}
๐Ÿ’ก Hint โ€” Card Hover Animation
.card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}
โœ… Full Solution: Complete index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home - Tasty Bites</title>
    <link rel="stylesheet" href="css/style.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
          rel="stylesheet">
</head>
<body>

    <header class="site-header">
        <nav class="navbar">
            <div class="nav-container">
                <a href="index.html" class="nav-logo">๐Ÿด Tasty Bites</a>
                <button class="nav-toggle" aria-label="Toggle navigation"
                        aria-expanded="false">โ˜ฐ</button>
                <ul class="nav-menu">
                    <li><a href="index.html" class="nav-link active">Home</a></li>
                    <li><a href="about.html" class="nav-link">About</a></li>
                    <li><a href="recipe.html" class="nav-link">Recipes</a></li>
                    <li><a href="contact.html" class="nav-link">Contact</a></li>
                </ul>
            </div>
        </nav>
    </header>

    <section class="hero">
        <div class="container">
            <h1>Simple Recipes,<br>Made with Love</h1>
            <p class="hero-tagline">
                A collection of my favorite recipes โ€” easy enough for
                weeknights, impressive enough for guests.
            </p>
            <a href="recipe.html" class="btn btn-primary">Browse Recipes</a>
        </div>
    </section>

    <main>
        <section class="features">
            <div class="container">
                <h2>What You'll Find Here</h2>
                <div class="card-grid">
                    <article class="card">
                        <div class="card-icon">๐Ÿ‘‹</div>
                        <h3>About Me</h3>
                        <p>Learn about the person behind the recipes โ€”
                           my cooking journey, favorite flavors, and kitchen
                           philosophy.</p>
                        <a href="about.html" class="btn btn-outline">Read More</a>
                    </article>
                    <article class="card">
                        <div class="card-icon">๐Ÿณ</div>
                        <h3>Recipes</h3>
                        <p>Browse tried-and-true recipes with clear
                           ingredients, step-by-step instructions, and tips
                           from real experience.</p>
                        <a href="recipe.html" class="btn btn-outline">Explore</a>
                    </article>
                    <article class="card">
                        <div class="card-icon">โœ‰๏ธ</div>
                        <h3>Get in Touch</h3>
                        <p>Have a question, a suggestion, or just want
                           to say hello? Drop me a message โ€” I'd love to
                           hear from you.</p>
                        <a href="contact.html" class="btn btn-outline">Contact</a>
                    </article>
                </div>
            </div>
        </section>

        <section class="featured-recipe">
            <div class="container">
                <h2>Featured Recipe</h2>
                <div class="featured-content">
                    <div class="featured-image">
                        <img src="images/recipe-pasta.jpg"
                             alt="A bowl of creamy garlic pasta topped with fresh herbs"
                             width="600" height="400">
                    </div>
                    <div class="featured-text">
                        <h3>Creamy Garlic Pasta</h3>
                        <p class="featured-meta">โฑ 25 min ยท ๐Ÿฝ Serves 4 ยท Easy</p>
                        <p>This one-pan pasta is my most-requested recipe. Creamy
                           without cream, packed with garlic, and ready in under
                           30 minutes. It's the ultimate weeknight dinner.</p>
                        <a href="recipe.html" class="btn btn-primary">View Recipe</a>
                    </div>
                </div>
            </div>
        </section>
    </main>

    <footer class="site-footer">
        <div class="container">
            <div class="footer-content">
                <div class="footer-info">
                    <p class="footer-logo">๐Ÿด Tasty Bites</p>
                    <p>Simple recipes, made with love.</p>
                </div>
                <nav class="footer-nav" aria-label="Footer navigation">
                    <h4>Pages</h4>
                    <ul>
                        <li><a href="index.html">Home</a></li>
                        <li><a href="about.html">About</a></li>
                        <li><a href="recipe.html">Recipes</a></li>
                        <li><a href="contact.html">Contact</a></li>
                    </ul>
                </nav>
            </div>
            <div class="footer-bottom">
                <p>&copy; 2026 Tasty Bites. All rights reserved.</p>
            </div>
        </div>
    </footer>

    <script src="js/script.js"></script>
</body>
</html>

๐ŸŽฏ Quick Quiz

Question 1: Why is the hero section's CTA an <a> tag instead of a <button>?

Question 2: What does flex: 1 1 280px mean?

Question 3: Why do we use position: sticky on the navbar?

Question 4: What does classList.toggle('open') do?

Question 5: Why do we put the mobile menu styles inside @media (max-width: 768px)?

Summary

๐ŸŽ‰ Key Takeaways

  • Build and style in small cycles โ€” write a section of HTML, style it, check the browser, move on
  • A sticky navigation bar uses position: sticky, Flexbox for layout, and a hamburger toggle for mobile
  • The hero section communicates your site's purpose instantly with a heading, tagline, and call-to-action
  • Use <a> for CTAs that navigate and <button> for CTAs that perform actions
  • Feature cards with flex: 1 1 280px and flex-wrap create a responsive grid with zero media queries
  • The featured recipe section uses the same flex-wrap trick for a side-by-side layout that stacks on mobile
  • The footer uses a dark background for visual separation and includes secondary navigation
  • One media query at 768px handles the mobile nav toggle and minor spacing adjustments
  • classList.toggle() is the cleanest way to show/hide elements with JavaScript
  • Always use aria-expanded on toggle buttons so screen readers know the state

๐Ÿ“ Your Project After This Lesson

my-website/
โ”œโ”€โ”€ css/
โ”‚   โ””โ”€โ”€ style.css      โ† base styles + nav + hero + cards + featured + footer + responsive
โ”œโ”€โ”€ images/
โ”‚   โ””โ”€โ”€ recipe-pasta.jpg   โ† featured recipe image (or placeholder)
โ”œโ”€โ”€ js/
โ”‚   โ””โ”€โ”€ script.js      โ† mobile menu toggle
โ”œโ”€โ”€ index.html         โ† โœ… COMPLETE โ€” nav, hero, cards, featured, footer
โ”œโ”€โ”€ about.html         โ† boilerplate (next lesson)
โ”œโ”€โ”€ recipe.html        โ† boilerplate (next lesson)
โ””โ”€โ”€ contact.html       โ† boilerplate (next lesson)

๐Ÿš€ What's Next?

Your homepage is built and responsive. In the next lesson โ€” Lesson 18: Building Content Pages โ€” you'll build the About, Recipe, and Contact pages. You'll reuse the nav and footer you just created, learn new layout patterns (profile sections, recipe cards, contact forms), and see how a consistent style guide makes multi-page development fast and enjoyable.