Build a Product Landing Page
Build a responsive landing page using semantic HTML, Flexbox, and CSS Grid.
Introduction
30 Second Summary
Every product you use online has a landing page that convinced you to try it. That first impression is built by frontend developers using just two languages: HTML and CSS.
In this project, you will build a complete landing page for a fictional app called Taskflow. You will use the same layout techniques that professional frontend teams ship every day.
What You'll Build
A responsive product landing page with a navigation bar, hero section, feature cards, testimonials, call-to-action section, and footer, built with modern CSS techniques including CSS Custom Properties, CSS Flexbox, and CSS Grid.
By the end of this project, you'll have:
- A semantic HTML file with proper document structure and accessible markup.
- A CSS stylesheet using custom properties, Flexbox, Grid, and media queries.
- A responsive layout that adapts cleanly from desktop to mobile viewports.
- Secret Mission: Add a dark/light mode toggle that previews your JavaScript journey.
Are there any prerequisites?
You only need a basic understanding of what code is. No HTML or CSS experience is required.
You should have VS Code installed with the Live Server extension, and Google Chrome as your browser.
Set Up Your Project Files
Before you can build your landing page, you need a project folder to hold your code and a live preview to see changes instantly as you work.
In this step, you'll create a project folder, write the HTML starter template, and connect everything to a live browser preview using the Live Server extension.
In this step, get ready to:
- Create a project folder with two files.
- Set up the HTML boilerplate and connect your stylesheet.
- Launch Live Server for instant browser previews.
Create your project folder and files
Every web project starts with a dedicated folder that keeps your files organized. You'll create one on your Desktop so it's easy to find.
- Open the integrated terminal in VS Code by pressing Ctrl+` (Control and the backtick key, located above Tab on most keyboards).
- A terminal panel appears at the bottom of VS Code.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir taskflow-landing
cd taskflow-landing
What does ~ mean?
The ~ symbol is a shortcut for your home folder. So ~/Desktop means "the Desktop folder inside my home folder." This works on both macOS and Windows.
- Create both project files by running:
macOS
touch index.html style.css
Windows
New-Item index.html -ItemType File
New-Item style.css -ItemType File
- Confirm the files exist by running:
ls
You should see index.html and style.css listed in the output.
- Now open this folder in VS Code. Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the taskflow-landing folder, and click Open.
VS Code reloads with your project folder in the Explorer sidebar on the left. You should see index.html and style.css listed there.
Don't see your files in the Explorer?
Make sure you selected the taskflow-landing folder itself, not a file inside it. If the sidebar is collapsed, click the top icon in the left sidebar (two overlapping documents) to expand it.
Help me find my project files in VS Code.
Set up the HTML boilerplate
Every HTML page needs a standard structure that tells the browser how to interpret and display the page. VS Code has a built-in shortcut called Emmet that generates this structure for you instantly.
- In the Explorer sidebar, click index.html to open it in the editor.
- Type ! (an exclamation mark) and press Tab.
VS Code fills the file with the standard HTML5 boilerplate. Now you need to make two small changes.
- Find this line:
<title>Document</title>
- Replace Document with Taskflow - Project Management Made Simple, so the line looks like this:
<title>Taskflow - Project Management Made Simple</title>
- Directly below the </title> line, add this new line to connect your CSS stylesheet:
<link rel="stylesheet" href="style.css">
- Delete any blank lines inside the <body></body> tags so the body section is empty.
What does the link tag do?
The <link rel="stylesheet" href="style.css"> tag tells the browser to load your CSS file and apply its styles to the page. Without this connection, nothing you write in style.css would have any effect.
- Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Your index.html should look exactly like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Taskflow - Project Management Made Simple</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
</body>
</html>
Your style.css file should be empty for now.
Launch Live Server
Live Server watches your project files and automatically refreshes the browser whenever you save a change. This gives you an instant feedback loop while you build.
- In the VS Code Explorer sidebar, right-click on index.html.
- Select Open with Live Server from the context menu.
Your default browser opens with a blank white page. Check the browser tab title. It should say "Taskflow - Project Management Made Simple".
Why is the page blank?
The page is blank because your <body> tag has no content yet. The title only shows in the browser tab, not on the page itself. You'll add visible content in the next step.
Browser didn't open automatically?
If no browser window appeared, look at the bottom-right of VS Code's status bar for a port number (usually Port: 5500). Open Google Chrome manually and go to http://127.0.0.1:5500 in the address bar.
If you don't see the port number, make sure the Live Server extension is installed by clicking the Extensions icon in the left sidebar and searching for "Live Server".
Help me get Live Server working in VS Code.
Your development environment is ready. Next up, you'll fill this blank page with the full HTML structure of your landing page.
Build the HTML Structure
You have a blank page with the right plumbing behind it. Now it's time to fill that page with actual content.
Every landing page follows a predictable structure: a navigation bar at the top, a big attention-grabbing headline, sections that explain the product, social proof from customers, a final push to sign up, and a footer. You will build all of these using semantic HTML elements that describe what the content IS, not just how it looks.
In this step, get ready to:
- Build the header and hero section with a navigation bar, headline, and call-to-action button.
- Build the features, testimonials, CTA, and footer sections to complete the page.
Build the header and navigation
The header is the first thing visitors see. It contains the brand name and links that let people jump to different sections of the page.
- In index.html, find the empty <body> tags.
- Add this header structure between the opening and closing body tags:
<header>
<nav class="navbar">
<div class="logo">Taskflow</div>
<ul class="nav-links">
<li><a href="#features">Features</a></li>
<li><a href="#testimonials">Testimonials</a></li>
<li><a href="#cta">Get Started</a></li>
</ul>
</nav>
</header>
What does this code do?
The <header> element tells the browser this is the top-level banner area. Inside it, <nav> marks the navigation links.
The <ul> is an unordered list. Each <li> contains an anchor link (<a>) that points to a section ID further down the page. When clicked, the browser scrolls to that section.
- Save index.html.
- Your browser auto-refreshes thanks to Live Server. You should see "Taskflow" followed by a bulleted list of three links: Features, Testimonials, and Get Started.
Don't see anything in the browser?
Make sure your code is between the <body> and </body> tags, not inside <head>. Content in the head is invisible to the page.
Also check that Live Server is still running. You should see "Go Live" or a port number in the VS Code status bar at the bottom.
Help me debug why my HTML content isn't showing in the browser.
Add the hero section
The hero section is the large, attention-grabbing area directly below the navigation. It delivers the main message of the page in one glance.
- In index.html, add this code directly below the closing </header> tag:
<section class="hero">
<div class="hero-content">
<h1>Manage Projects Without the Chaos</h1>
<p>Taskflow helps teams stay organized, hit deadlines, and actually enjoy their work. No complicated setup. Just clarity.</p>
<a href="#cta" class="btn btn-primary">Start Free Trial</a>
</div>
</section>
What does this code do?
The <section> element groups related content together. This hero section contains a headline (<h1>), a supporting paragraph, and an anchor link styled as a button.
The class="btn btn-primary" attribute assigns two CSS classes to the link. Later, you will style these classes to make it look like a button.
- Save index.html.
- Your browser should now show the headline "Manage Projects Without the Chaos", a paragraph of text below it, and a "Start Free Trial" link.
See the text but it looks jumbled together?
That is completely normal. Without CSS, the browser displays everything in its default style. The headline should be large and bold, the paragraph in regular text, and the link underlined and blue. Styling comes in the next step.
What is semantic HTML?
Semantic elements like <header>, <nav>, <section>, and <footer> describe the purpose of the content, not just its appearance. A <div> is a generic container that says nothing about what's inside.
Search engines and screen readers use semantic tags to understand your page structure. Professional codebases always use them.
Build the features section
Now you will add the remaining sections that make up the rest of the page. The features section uses a card pattern that repeats three times.
- In index.html, add this code directly below the closing </section> tag of the hero section:
<section id="features" class="features">
<h2>Everything You Need</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">📋</div>
<h3>Smart Task Boards</h3>
<p>Drag, drop, and organize tasks visually. See your entire project at a glance.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⏱️</div>
<h3>Built-in Time Tracking</h3>
<p>Know exactly where time goes. Automatic reports keep everyone accountable.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤝</div>
<h3>Team Collaboration</h3>
<p>Comments, file sharing, and real-time updates. No more endless email threads.</p>
</div>
</div>
</section>
What does this code do?
This section has an id="features" attribute. That is what the navigation link href="#features" from earlier connects to. Clicking that link scrolls the page here.
The features-grid wrapper groups three feature-card divs. In a later step, you will use CSS Grid to arrange them into columns.
- Save index.html.
- You should see "Everything You Need" as a heading, followed by three blocks of text each with an emoji icon, a bold subheading, and a description paragraph.
Emojis showing as empty boxes?
Make sure your file is saved with UTF-8 encoding (the default in VS Code). If you still see empty boxes, try a different browser. Most modern browsers render emoji natively.
Help me figure out why emojis aren't rendering in my HTML page.
Add the testimonials section
- Below the closing </section> of the features section, add the testimonials:
<section id="testimonials" class="testimonials">
<h2>Loved by Teams Everywhere</h2>
<div class="testimonial-container">
<div class="testimonial-card">
<p class="testimonial-text">"Taskflow replaced three different tools for us. Our team ships faster and communicates better."</p>
<p class="testimonial-author">— Sarah Chen, Product Lead at Buildworks</p>
</div>
<div class="testimonial-card">
<p class="testimonial-text">"The simplicity is what sold me. We were up and running in 10 minutes, not 10 days."</p>
<p class="testimonial-author">— Marcus Rivera, CTO at Launchpad</p>
</div>
</div>
</section>
What does this code do?
The testimonial-container wraps two testimonial-card divs. Each card has a quote (testimonial-text) and an author attribution (testimonial-author).
Later, you will use CSS Flexbox to place these cards side by side.
- Save index.html.
- You should see "Loved by Teams Everywhere" followed by two quoted testimonials with author names.
Testimonial quotes looking wrong?
Make sure the opening and closing quotation marks are regular straight quotes, not curly/smart quotes. VS Code sometimes auto-converts them. The text should start and end with the standard double-quote character.
Add the CTA section and footer
The CTA (call-to-action) section gives the visitor one final push to sign up. The footer closes the page with utility links.
- Below the closing </section> of the testimonials section, add the CTA section:
<section id="cta" class="cta">
<h2>Ready to Get Organized?</h2>
<p>Join 10,000+ teams already using Taskflow. Free for up to 5 users.</p>
<a href="#" class="btn btn-primary">Start Your Free Trial</a>
</section>
- Now add the footer directly below the CTA section:
<footer class="footer">
<div class="footer-content">
<div class="footer-logo">Taskflow</div>
<ul class="footer-links">
<li><a href="#">Privacy</a></li>
<li><a href="#">Terms</a></li>
<li><a href="#">Contact</a></li>
</ul>
</div>
<p class="footer-copyright">© 2026 Taskflow. All rights reserved.</p>
</footer>
What does this code do?
The CTA section reuses the same btn btn-primary classes from the hero. When you style those classes later, both buttons will match automatically.
The <footer> element is a semantic tag that tells the browser (and screen readers) this is the page's closing information area. The links use href="#" as a placeholder since there are no real pages to link to yet.
- Save index.html.
- Scroll to the bottom of your browser page. You should see "Ready to Get Organized?" with a link below it, then the Taskflow logo, three footer links (Privacy, Terms, Contact), and a copyright line.
Some sections missing from the page?
Check that every opening tag has a matching closing tag. A missing </section> or </div> can hide everything below it.
Count your section tags. You should have four sections total (hero, features, testimonials, cta), each with its own closing tag.
Help me find the missing closing tag in my HTML.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file, then scroll through the page to confirm all six sections appear in order: navigation, hero, features, testimonials, CTA, and footer.
ⓧ I'd like to double check the full code
Here is the complete index.html file. Compare it against yours to find any differences.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Taskflow - Project Management Made Simple</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav class="navbar">
<div class="logo">Taskflow</div>
<ul class="nav-links">
<li><a href="#features">Features</a></li>
<li><a href="#testimonials">Testimonials</a></li>
<li><a href="#cta">Get Started</a></li>
</ul>
</nav>
</header>
<section class="hero">
<div class="hero-content">
<h1>Manage Projects Without the Chaos</h1>
<p>Taskflow helps teams stay organized, hit deadlines, and actually enjoy their work. No complicated setup. Just clarity.</p>
<a href="#cta" class="btn btn-primary">Start Free Trial</a>
</div>
</section>
<section id="features" class="features">
<h2>Everything You Need</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">📋</div>
<h3>Smart Task Boards</h3>
<p>Drag, drop, and organize tasks visually. See your entire project at a glance.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⏱️</div>
<h3>Built-in Time Tracking</h3>
<p>Know exactly where time goes. Automatic reports keep everyone accountable.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤝</div>
<h3>Team Collaboration</h3>
<p>Comments, file sharing, and real-time updates. No more endless email threads.</p>
</div>
</div>
</section>
<section id="testimonials" class="testimonials">
<h2>Loved by Teams Everywhere</h2>
<div class="testimonial-container">
<div class="testimonial-card">
<p class="testimonial-text">"Taskflow replaced three different tools for us. Our team ships faster and communicates better."</p>
<p class="testimonial-author">— Sarah Chen, Product Lead at Buildworks</p>
</div>
<div class="testimonial-card">
<p class="testimonial-text">"The simplicity is what sold me. We were up and running in 10 minutes, not 10 days."</p>
<p class="testimonial-author">— Marcus Rivera, CTO at Launchpad</p>
</div>
</div>
</section>
<section id="cta" class="cta">
<h2>Ready to Get Organized?</h2>
<p>Join 10,000+ teams already using Taskflow. Free for up to 5 users.</p>
<a href="#" class="btn btn-primary">Start Your Free Trial</a>
</section>
<footer class="footer">
<div class="footer-content">
<div class="footer-logo">Taskflow</div>
<ul class="footer-links">
<li><a href="#">Privacy</a></li>
<li><a href="#">Terms</a></li>
<li><a href="#">Contact</a></li>
</ul>
</div>
<p class="footer-copyright">© 2026 Taskflow. All rights reserved.</p>
</footer>
</body>
</html>
Your page now has all the content it needs. Every section exists in the correct order with meaningful class names ready to style. Next up, you will transform this plain text into a polished layout using CSS custom properties and Flexbox.
Add Base Styles and Navigation Layout
Your HTML structure is solid, but right now the browser is rendering everything in its default style. Every heading looks the same weight, links are blue and underlined, and there is no sense of visual hierarchy.
In this step, you will build a CSS foundation using custom properties, then use CSS Flexbox to transform the navigation into a professional horizontal bar.
In this step, get ready to:
- Define CSS custom properties for a consistent color palette and spacing system.
- Style the navigation bar with Flexbox for a logo-left, links-right layout.
- Style the hero section and CTA button with hover effects.
Set up your CSS foundation
Before styling individual components, professional codebases define a set of reusable values (colors, spacing, fonts) in one place. CSS Custom Properties let you do exactly this. Change a color once and every element that references it updates automatically.
- Switch back to style.css in VS Code.
- Add your custom properties inside a :root selector by pasting this code:
:root {
--color-primary: #4f46e5;
--color-primary-hover: #4338ca;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-background: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
--max-width: 1120px;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 4rem;
--border-radius: 8px;
}
What does this code do?
The :root selector targets the top-level element of your document. Any variable defined here is available to every other CSS rule in your stylesheet.
Each --color-* variable stores a hex color. The --spacing-* variables define a consistent spacing scale. Later, you will reference these with var(--color-primary) instead of repeating raw hex values everywhere.
- Save style.css.
- Check your browser. You will not see any visual change yet because defining variables does not apply them to anything.
- Below your :root block, add a universal reset and body typography styles:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-background);
line-height: 1.6;
}
What does this code do?
The * selector targets every element on the page. It removes default margins and padding that browsers add, and sets box-sizing: border-box so padding never makes elements wider than you expect.
The body rule applies your custom property values as the default font, text color, and background for the entire page.
- Save style.css and check your browser.
- You should see the text change from the browser's default serif font to a clean sans-serif. All the awkward spacing between elements should collapse.
Text still looks the same?
Make sure you saved style.css (not just index.html). Live Server only reloads when the changed file is saved.
Confirm your <link> tag in index.html points to style.css (the exact filename, case-sensitive).
help me debug why my CSS styles aren't showing up
Style the navigation with Flexbox
Flexbox is a CSS layout system designed for arranging items in a single row or column. It is the standard way to build navigation bars because it handles spacing and alignment with just a few properties.
- Below your body rule, add the navigation styles:
/* Navigation */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-primary);
}
.nav-links {
display: flex;
list-style: none;
gap: var(--spacing-lg);
}
.nav-links a {
text-decoration: none;
color: var(--color-text);
font-weight: 500;
transition: color 0.2s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
What does this code do?
Setting display: flex on .navbar turns it into a flex container. justify-content: space-between pushes the logo to the left edge and links to the right edge. align-items: center vertically centers everything in the row.
The .nav-links list is also a flex container with gap: var(--spacing-lg) to space links evenly. The transition property makes the color change animate smoothly on hover instead of snapping instantly.
- Save style.css and check your browser.
- You should see "Taskflow" in purple on the left and three navigation links spaced evenly on the right. Hovering over a link smoothly changes its color to purple.
Links still stacked vertically?
Check that your .nav-links rule has display: flex (not just .navbar). The outer flex makes the logo and list side-by-side. The inner flex on .nav-links makes the list items sit in a row.
Also confirm list-style: none is on .nav-links to remove the bullet points.
help me fix my Flexbox navigation layout
Style the hero section and CTA button
The hero section is the first thing visitors see. It needs a large headline, a readable description, and a clear call-to-action button that invites interaction.
- Below your navigation styles, add the hero section rules:
/* Hero Section */
.hero {
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.hero h1 {
font-size: 3rem;
font-weight: 800;
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.hero p {
font-size: 1.25rem;
color: var(--color-text-light);
max-width: 600px;
margin: 0 auto var(--spacing-lg);
}
What does this code do?
The text-align: center on .hero centers all inline content inside it. The max-width: var(--max-width) with margin: 0 auto prevents the section from stretching too wide on large monitors.
The paragraph gets its own max-width: 600px so lines of text stay short and readable. The margin: 0 auto centers it within the already-centered hero.
- Save style.css and check your browser.
- You should see a large, bold headline centered on the page with a shorter description below it.
- Below the hero styles, add the button styles:
/* Buttons */
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
border-radius: var(--border-radius);
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.btn-primary {
background-color: var(--color-primary);
color: #ffffff;
}
.btn-primary:hover {
background-color: var(--color-primary-hover);
transform: translateY(-2px);
}
What does this code do?
The .btn class sets up base button styling that any button on the page can share. The .btn-primary class adds the specific purple color on top.
The transform: translateY(-2px) on hover shifts the button 2 pixels upward, creating a subtle "lift" effect. Combined with the transition property, this makes the button feel interactive and responsive to the cursor.
- Save style.css and check your browser.
- You should see a purple "Start Free Trial" button below the hero description. Hover over it to see it lift slightly and darken.
Button not lifting on hover?
Make sure both .btn and .btn-primary rules are present. The transition lives on .btn (not on the hover rule) so the browser knows to animate the change.
If the button appears as plain text, confirm your HTML uses class="btn btn-primary" (both classes together on the same element).
help me debug my CSS button hover effect
✔️ Awesome, I've got everything!
Great. Double check you have saved style.css and your browser shows the styled navigation and hero.
ⓧ I'd like to double check the full code
Here is the complete style.css file at the end of this step:
:root {
--color-primary: #4f46e5;
--color-primary-hover: #4338ca;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-background: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
--max-width: 1120px;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 4rem;
--border-radius: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-background);
line-height: 1.6;
}
/* Navigation */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-primary);
}
.nav-links {
display: flex;
list-style: none;
gap: var(--spacing-lg);
}
.nav-links a {
text-decoration: none;
color: var(--color-text);
font-weight: 500;
transition: color 0.2s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
/* Hero Section */
.hero {
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.hero h1 {
font-size: 3rem;
font-weight: 800;
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.hero p {
font-size: 1.25rem;
color: var(--color-text-light);
max-width: 600px;
margin: 0 auto var(--spacing-lg);
}
/* Buttons */
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
border-radius: var(--border-radius);
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.btn-primary {
background-color: var(--color-primary);
color: #ffffff;
}
.btn-primary:hover {
background-color: var(--color-primary-hover);
transform: translateY(-2px);
}
Why CSS custom properties instead of hardcoded values?
Every color, spacing value, and font in this project references a custom property from :root. This means you can change a color in one place and every rule that uses it updates instantly.
This pattern is how professional design systems work. In the Secret Mission later, you will see custom properties make dark mode possible with just a few overrides.
Your page already looks polished at the top. Next up, you will use CSS Grid to turn those feature cards into a professional three-column layout.
Style the Feature Cards with CSS Grid
Your navbar and hero look polished, but the features section is still a wall of unstyled text. You need the three feature cards to sit side-by-side in equal columns.
This is exactly what CSS Grid is built for. Where Flexbox aligns items along a single axis, Grid gives you control over both rows and columns at once. You define a grid template, and the browser places your cards into it automatically.
In this step, get ready to:
- Create a three-column grid layout for the features section.
- Style individual feature cards with borders, padding, and hover effects.
Create the grid layout
The features section needs a background color to visually separate it from the hero above. Then the .features-grid container becomes the grid parent that controls how the three cards arrange themselves.
- In style.css, scroll to the bottom of your file (below the .btn-primary:hover rule).
- Add the features section and grid styles by pasting this code:
/* Features Section */
.features {
padding: var(--spacing-xl) var(--spacing-lg);
background-color: var(--color-surface);
}
.features h2 {
text-align: center;
font-size: 2rem;
margin-bottom: var(--spacing-xl);
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
What does this code do?
- .features adds padding and a light grey background (var(--color-surface)) so the section stands apart from the white hero above.
- display: grid turns .features-grid into a grid container. Its direct children (the three feature cards) become grid items.
- grid-template-columns: repeat(3, 1fr) creates three columns of equal width. 1fr means "one fraction of the available space."
- gap: var(--spacing-lg) adds equal spacing between every card without needing margins on individual items.
- Save style.css and check your browser.
- You should see the three feature sections sitting side-by-side in equal columns on a light grey background. The heading "Everything You Need" is centered above them.
Cards not sitting side-by-side?
- Confirm your CSS targets .features-grid (with a hyphen). This must match the class name in your HTML exactly.
- Make sure display: grid is inside the .features-grid rule, not the .features rule.
- Check that every opening brace { has a matching closing brace }. A missing brace from the previous section will break everything below it.
Why aren't my CSS Grid columns working?
Style the feature cards
The cards are in the right position, but they have no visual definition. Each card needs a background, border, and a hover effect to make them feel interactive.
- Below your .features-grid rule, add the card styles by pasting this code:
.feature-card {
background-color: var(--color-background);
padding: var(--spacing-lg);
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
.feature-icon {
font-size: 2rem;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: var(--spacing-sm);
}
.feature-card p {
color: var(--color-text-light);
}
What does this code do?
- .feature-card gives each card a white background, padding, rounded corners, and a subtle border. The transition property tells the browser to animate changes to transform and box-shadow smoothly over 0.2 seconds.
- .feature-card:hover lifts the card upward by 4 pixels and adds a soft shadow underneath. This creates the feeling that the card is physically rising off the page.
- The icon, heading, and paragraph styles control spacing and text color inside each card.
Why Grid instead of Flexbox here?
You used Flexbox for the navbar because it needed one-dimensional alignment (items in a row). Grid is the better choice for the feature cards because you need equal-width columns that stay perfectly aligned regardless of content length.
With Flexbox, cards would shrink or grow based on their content. Grid enforces the column structure, so all three cards stay the same width even if one has more text.
- Save style.css and refresh your browser.
- You should see three polished cards with rounded borders and emoji icons. Hover over any card to see it lift smoothly with a shadow appearing underneath.
Hover effect not working?
- Make sure the transition property is on .feature-card (not on .feature-card:hover). The transition defines how the element animates, so it must be on the base state.
- Check that .feature-card:hover has no space before the colon. A space would target a child element instead of the hover state.
Why isn't my CSS hover transition animating smoothly?
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the full style.css file up to this point. Compare it with yours line by line.
:root {
--color-primary: #4f46e5;
--color-primary-hover: #4338ca;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-background: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
--max-width: 1120px;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 4rem;
--border-radius: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-background);
line-height: 1.6;
}
/* Navigation */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-primary);
}
.nav-links {
display: flex;
list-style: none;
gap: var(--spacing-lg);
}
.nav-links a {
text-decoration: none;
color: var(--color-text);
font-weight: 500;
transition: color 0.2s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
/* Hero Section */
.hero {
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.hero h1 {
font-size: 3rem;
font-weight: 800;
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.hero p {
font-size: 1.25rem;
color: var(--color-text-light);
max-width: 600px;
margin: 0 auto var(--spacing-lg);
}
/* Buttons */
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
border-radius: var(--border-radius);
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.btn-primary {
background-color: var(--color-primary);
color: #ffffff;
}
.btn-primary:hover {
background-color: var(--color-primary-hover);
transform: translateY(-2px);
}
/* Features Section */
.features {
padding: var(--spacing-xl) var(--spacing-lg);
background-color: var(--color-surface);
}
.features h2 {
text-align: center;
font-size: 2rem;
margin-bottom: var(--spacing-xl);
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.feature-card {
background-color: var(--color-background);
padding: var(--spacing-lg);
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
.feature-icon {
font-size: 2rem;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: var(--spacing-sm);
}
.feature-card p {
color: var(--color-text-light);
}
Your feature cards look sharp. Next up, you will style the remaining sections and make the entire page responsive so it looks great on phones and tablets too.
Complete the Page and Make It Responsive
Your page has a polished top half, but the testimonials, call-to-action, and footer are still raw text. Beyond styling those sections, you also need the layout to work on smaller screens.
In this step, you will finish styling every remaining section with Flexbox, then add a media query that reorganizes the entire page for mobile devices.
In this step, get ready to:
- Style the testimonials, CTA, and footer sections.
- Add a responsive media query that switches to a single-column layout on mobile.
- Test your page at multiple viewport widths using Chrome DevTools.
Style the testimonials section
The testimonials section needs two quote cards sitting side by side. Flexbox is perfect here because it handles one-dimensional alignment in a row.
- In style.css, add the following CSS below your existing .feature-card p rule:
/* Testimonials Section */
.testimonials {
padding: var(--spacing-xl) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.testimonials h2 {
text-align: center;
font-size: 2rem;
margin-bottom: var(--spacing-xl);
}
.testimonial-container {
display: flex;
gap: var(--spacing-lg);
justify-content: center;
}
.testimonial-card {
background-color: var(--color-surface);
padding: var(--spacing-lg);
border-radius: var(--border-radius);
max-width: 500px;
border: 1px solid var(--color-border);
}
.testimonial-text {
font-style: italic;
margin-bottom: var(--spacing-md);
color: var(--color-text);
}
.testimonial-author {
font-weight: 600;
color: var(--color-text-light);
font-size: 0.875rem;
}
What does this code do?
- .testimonial-container uses display: flex to place the two cards in a horizontal row with centered alignment.
- .testimonial-card caps each card at 500px wide so they don't stretch across the full page.
- .testimonial-text uses font-style: italic to visually distinguish quotes from regular body text.
- Save style.css.
- Refresh your browser. You should see two styled quote cards sitting side by side with a light background, rounded corners, and italic text.
Cards stacking vertically instead of side by side?
Make sure display: flex is on .testimonial-container, not on .testimonial-card. The container is the parent that arranges its children in a row.
Also check that the class name in your CSS matches the HTML exactly: testimonial-container (with a hyphen, no typos).
Why aren't my testimonial cards displaying side by side?
Style the CTA section and footer
The call-to-action section uses a bold colored background to visually break from the rest of the page. The footer mirrors the navbar layout with Flexbox.
- In style.css, add the CTA styles below your testimonial rules:
/* CTA Section */
.cta {
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
background-color: var(--color-primary);
color: #ffffff;
}
.cta h2 {
font-size: 2rem;
margin-bottom: var(--spacing-md);
}
.cta p {
font-size: 1.125rem;
margin-bottom: var(--spacing-lg);
opacity: 0.9;
}
.cta .btn-primary {
background-color: #ffffff;
color: var(--color-primary);
}
.cta .btn-primary:hover {
background-color: #f3f4f6;
}
What does this code do?
- .cta sets the primary indigo as the background color with white text, creating a high-contrast section that grabs attention.
- .cta .btn-primary inverts the button colors (white background, indigo text) so the button stands out against the colored section.
- Now add the footer styles directly below the CTA rules:
/* Footer */
.footer {
padding: var(--spacing-lg);
border-top: 1px solid var(--color-border);
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: var(--max-width);
margin: 0 auto var(--spacing-md);
}
.footer-logo {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-primary);
}
.footer-links {
display: flex;
list-style: none;
gap: var(--spacing-lg);
}
.footer-links a {
text-decoration: none;
color: var(--color-text-light);
transition: color 0.2s ease;
}
.footer-links a:hover {
color: var(--color-primary);
}
.footer-copyright {
text-align: center;
color: var(--color-text-light);
font-size: 0.875rem;
}
What does this code do?
- .footer-content uses the same Flexbox pattern as the navbar: justify-content: space-between pushes the logo left and links right.
- .footer-copyright sits below the flex row with centered text, creating a clean two-level footer.
- Save style.css.
- Refresh your browser. You should see a bold indigo CTA section with a white button, and a footer with the logo on the left and links on the right.
CTA button invisible against the background?
Check that your selector is .cta .btn-primary (with a space between .cta and .btn-primary). Without the space, CSS treats it as a single element with both classes, which won't match your HTML.
Why is my CTA button not showing white on the colored background?
Add a media query for mobile
Your page looks great on a wide screen, but on a phone, three-column grids and side-by-side cards become cramped and unreadable. A media query lets you define different styles that activate only below a certain screen width.
- At the very bottom of style.css, add this responsive media query:
/* Responsive Design */
@media (max-width: 768px) {
.navbar {
flex-direction: column;
gap: var(--spacing-md);
}
.hero h1 {
font-size: 2rem;
}
.hero p {
font-size: 1rem;
}
.features-grid {
grid-template-columns: 1fr;
}
.testimonial-container {
flex-direction: column;
align-items: center;
}
.footer-content {
flex-direction: column;
gap: var(--spacing-md);
text-align: center;
}
}
What does this code do?
Everything inside @media (max-width: 768px) only applies when the browser window is 768 pixels wide or narrower (roughly tablet-sized and below).
- flex-direction: column stacks items vertically instead of horizontally. This converts the navbar, testimonials, and footer from rows into columns.
- grid-template-columns: 1fr collapses the three-column feature grid into a single column so each card gets the full width.
- The reduced font sizes (2rem and 1rem) prevent text from overflowing on small screens.
- Save style.css.
- Resize your browser window to be narrower. You should see the navbar links stack below the logo and the feature cards switch to a single column.
Layout not changing when you resize?
Make sure the @media block is at the very bottom of your stylesheet. If it appears above the original rules, the original rules override it because they come later in the file.
Also check that every opening brace { has a matching closing brace }. The media query needs its own outer closing brace after all the rules inside it.
Why isn't my media query working when I resize the browser?
✔️ Awesome, I've got everything!
Great. Double check you've saved style.css before moving on.
ⓧ I'd like to double check the full code
Here is the complete style.css file. Compare yours against it line by line.
:root {
--color-primary: #4f46e5;
--color-primary-hover: #4338ca;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-background: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
--max-width: 1120px;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 4rem;
--border-radius: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-background);
line-height: 1.6;
}
/* Navigation */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-primary);
}
.nav-links {
display: flex;
list-style: none;
gap: var(--spacing-lg);
}
.nav-links a {
text-decoration: none;
color: var(--color-text);
font-weight: 500;
transition: color 0.2s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
/* Hero Section */
.hero {
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.hero h1 {
font-size: 3rem;
font-weight: 800;
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.hero p {
font-size: 1.25rem;
color: var(--color-text-light);
max-width: 600px;
margin: 0 auto var(--spacing-lg);
}
/* Buttons */
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
border-radius: var(--border-radius);
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.btn-primary {
background-color: var(--color-primary);
color: #ffffff;
}
.btn-primary:hover {
background-color: var(--color-primary-hover);
transform: translateY(-2px);
}
/* Features Section */
.features {
padding: var(--spacing-xl) var(--spacing-lg);
background-color: var(--color-surface);
}
.features h2 {
text-align: center;
font-size: 2rem;
margin-bottom: var(--spacing-xl);
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.feature-card {
background-color: var(--color-background);
padding: var(--spacing-lg);
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
.feature-icon {
font-size: 2rem;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: var(--spacing-sm);
}
.feature-card p {
color: var(--color-text-light);
}
/* Testimonials Section */
.testimonials {
padding: var(--spacing-xl) var(--spacing-lg);
max-width: var(--max-width);
margin: 0 auto;
}
.testimonials h2 {
text-align: center;
font-size: 2rem;
margin-bottom: var(--spacing-xl);
}
.testimonial-container {
display: flex;
gap: var(--spacing-lg);
justify-content: center;
}
.testimonial-card {
background-color: var(--color-surface);
padding: var(--spacing-lg);
border-radius: var(--border-radius);
max-width: 500px;
border: 1px solid var(--color-border);
}
.testimonial-text {
font-style: italic;
margin-bottom: var(--spacing-md);
color: var(--color-text);
}
.testimonial-author {
font-weight: 600;
color: var(--color-text-light);
font-size: 0.875rem;
}
/* CTA Section */
.cta {
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
background-color: var(--color-primary);
color: #ffffff;
}
.cta h2 {
font-size: 2rem;
margin-bottom: var(--spacing-md);
}
.cta p {
font-size: 1.125rem;
margin-bottom: var(--spacing-lg);
opacity: 0.9;
}
.cta .btn-primary {
background-color: #ffffff;
color: var(--color-primary);
}
.cta .btn-primary:hover {
background-color: #f3f4f6;
}
/* Footer */
.footer {
padding: var(--spacing-lg);
border-top: 1px solid var(--color-border);
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: var(--max-width);
margin: 0 auto var(--spacing-md);
}
.footer-logo {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-primary);
}
.footer-links {
display: flex;
list-style: none;
gap: var(--spacing-lg);
}
.footer-links a {
text-decoration: none;
color: var(--color-text-light);
transition: color 0.2s ease;
}
.footer-links a:hover {
color: var(--color-primary);
}
.footer-copyright {
text-align: center;
color: var(--color-text-light);
font-size: 0.875rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.navbar {
flex-direction: column;
gap: var(--spacing-md);
}
.hero h1 {
font-size: 2rem;
}
.hero p {
font-size: 1rem;
}
.features-grid {
grid-template-columns: 1fr;
}
.testimonial-container {
flex-direction: column;
align-items: center;
}
.footer-content {
flex-direction: column;
gap: var(--spacing-md);
text-align: center;
}
}
Test responsiveness in Chrome DevTools
Resizing the browser window gives you a rough sense of responsiveness. Chrome DevTools lets you test at exact pixel widths that match real devices.
- In Chrome, press F12 to open DevTools.
- Click the device toggle icon in the DevTools toolbar (it looks like a phone and tablet overlapping).
- In the viewport width dropdown at the top, type 375 to simulate a mobile phone width. Verify that all content stacks in a single column with no horizontal scrolling.
- Change the width to 768 to see the breakpoint boundary. The layout should still be in single-column mode.
- Change the width to 1200 to confirm the full desktop layout returns with three feature columns and side-by-side testimonials.
What is responsive design?
Responsive design means your page adapts its layout to fit any screen size. Instead of building separate pages for mobile and desktop, you write one HTML file and use Media Queries to adjust the CSS at different widths.
Professional teams test at common breakpoints (375px for phones, 768px for tablets, 1200px+ for desktops) to make sure nothing breaks.
Secret mission
Add a Dark Mode Toggle
What if your entire color scheme could flip with a single click? In this secret mission, you'll add a dark mode toggle that demonstrates the true power of CSS custom properties. You'll also get your first taste of JavaScript.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files for future reference or remove them from your computer.
Resources you used:
- The taskflow-landing folder on your Desktop (contains index.html and style.css).
Keep everything running
No action needed. Choose this if you plan to keep building on your landing page or want it as a portfolio piece.
- Your taskflow-landing folder stays on your Desktop, ready to open in VS Code anytime.
- You can publish it online later using GitHub Pages (free) when you learn Git in a future project.
Pause - I'll come back to this later
Since this is a local project with no running processes, there is nothing to pause. Your files sit on your Desktop until you are ready to return.
- Stop Live Server by clicking the port number in the VS Code status bar (bottom right) to close the local server.
- Close the browser tab showing your landing page.
- Your files remain unchanged and ready to reopen whenever you want.
Delete - I don't want to use this again
Remove all project files from your computer. This permanently deletes your work.
- Stop Live Server by clicking the port number in the VS Code status bar (bottom right).
- Close the project in VS Code.
- Delete the taskflow-landing folder from your Desktop by running one of these commands in your terminal:
macOS
rm -rf ~/Desktop/taskflow-landing
Windows
rmdir /s /q %USERPROFILE%\Desktop\taskflow-landing
- Confirm deletion by running ls ~/Desktop (macOS) or dir %USERPROFILE%\Desktop (Windows). The taskflow-landing folder should no longer appear.
Nice Work!
Nice Work!
You just built a complete, responsive product landing page from scratch using pure HTML and CSS. This is exactly the kind of work frontend developers do every day.
You've learned how to:
- Build a complete product landing page with semantic HTML elements like header, nav, section, and footer.
- Lay out navigation and content with CSS Flexbox, and build a responsive card grid with CSS Grid.
- Create a maintainable color system with CSS custom properties and make the entire page adapt to any screen size with a media query.
- Secret Mission: Implemented a dark/light mode toggle using CSS custom properties and JavaScript class toggling.
Ready to quiz yourself?