Build a Personal Portfolio Website

Build a responsive portfolio site with HTML, CSS, and Flexbox.

Introduction

30 Second Summary

Every developer needs a home on the internet. A personal portfolio is how you introduce yourself, show what you've built, and give people a way to reach you.

In this project, you will build a single-page portfolio website from scratch using HTML and CSS. You'll end up with a polished page that works on any screen size, complete with hover effects and a colour theme you can change by editing a single line.

What You'll Build

Imagine showing someone your finished portfolio on your phone and your laptop, watching the layout adapt perfectly to both screens while project cards lift to life as you hover over them.

By the end of this project, you'll have:

  • A hero section with your name, tagline, and a call-to-action button that scrolls visitors down to your work.
  • A project card grid built with Flexbox that arranges your work side by side and lifts each card when you hover over it.
  • A responsive layout that automatically stacks content vertically on mobile screens, so your site looks great on any device.
  • Secret Mission: Add a dark mode toggle that flips your entire colour scheme with one click.

Are there any prerequisites?

None. This guide walks you through setting up everything you need, including VS Code and the Live Preview extension. All you need is a computer with Google Chrome installed.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters to you.

Set Up Your Project

Every website starts with a home on your computer. You need a dedicated folder to hold your project files, and a way to see your changes appear live as you type them.

In this step, you will create your project folder, set up two files (one for content, one for style), and install an extension that previews your webpage inside VS Code in real time.

In this step, get ready to:
  • Create a portfolio folder with index.html and style.css files.
  • Install the Live Preview extension for real-time previewing.
  • Write your first HTML and see it render live.
Create your project folder

Your portfolio needs a dedicated folder on your Desktop so you can find it easily later.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Open the integrated terminal by clicking Terminal in the menu bar, then selecting New Terminal.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder by running this command:
mkdir portfolio
  • Confirm the folder was created by running:
ls

You should see portfolio listed in the output.

Don't see the portfolio folder?

Make sure you ran cd ~/Desktop first. If ls shows many files but no portfolio, you may have a typo in the mkdir command. Try running it again.

Help me troubleshoot this

Open the folder and install Live Preview

Now you will open the portfolio folder as your workspace in VS Code. This tells VS Code to treat that folder as your project root.

  • Click File in the VS Code menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the portfolio folder, and click Open.

The Explorer sidebar on the left now shows your empty portfolio folder.

  • In the Explorer sidebar, hover over the folder name and click the New File icon (a page with a plus sign).
  • Type index.html and press Enter.
  • Click the New File icon again and type style.css, then press Enter.

You should now see both index.html and style.css listed in the Explorer sidebar.

Next, you need a way to preview your webpage live as you edit it. The Live Preview extension by Microsoft launches a local development server with live reload, so changes appear instantly without refreshing.

  • Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
  • Type Live Preview in the search box.
  • Find the result published by Microsoft and click Install.

After a moment the Install button disappears, confirming the extension is ready.

Not sure which result to pick?

Look for the one that says "Microsoft" directly below the extension name. Other extensions may have similar names, but only the Microsoft one provides the in-editor preview panel you need for this project.

Help me find the right extension

Write your first HTML and preview it

HTML is the language that gives a webpage its structure and content. Every webpage starts with an HTML skeleton that tells the browser what kind of document it is reading.

  • Click on index.html in the Explorer sidebar to open it in the editor.
  • Type the following HTML skeleton into the file:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Portfolio</title>
</head>
<body>
  <h1>Hello!</h1>
</body>
</html>

What does this code do?

  • <!DOCTYPE html> tells the browser this is a modern HTML5 document.
  • <head> holds invisible metadata like the page title and character encoding.
  • <body> holds everything the user actually sees on the page.
  • <h1>Hello!</h1> creates a top-level heading with the text "Hello!" so you have something visible to confirm the preview works.
  • Save the file by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Before you preview, what do you think you will see in the panel? A blank page, or something else?

  • Right-click anywhere in the index.html editor and select Show Preview.

A preview panel opens beside your code showing "Hello!" in large text. You just wrote your first HTML and confirmed it renders correctly.

Don't see Show Preview in the menu?

This option only appears after the Live Preview extension is installed. Go back to the Extensions panel and confirm that Live Preview by Microsoft shows a gear icon (meaning it is installed and active). If it still says Install, click it and wait for it to finish.

Help me get Live Preview working

✔️ Awesome, I've got everything!

Great. Make sure you have saved index.html and that the preview panel shows "Hello!" next to your code.

ⓧ 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>My Portfolio</title>
</head>
<body>
  <h1>Hello!</h1>
</body>
</html>

Your style.css file should be empty for now. You will add styles in a later step.

Your workspace is ready. Next up, you will fill your page with real content and build out the full structure of your portfolio.

Build Your Page Structure

Your preview panel is live and your development environment is set up. Now it's time to fill the page with actual content that visitors will read.

Before you can style anything, you need content on the page. In this step, you'll write all the HTML structure and connect a stylesheet so you have raw material ready for visual design.

In this step, get ready to:
  • Write the full HTML structure with hero, about, projects, and contact sections.
  • Connect the CSS stylesheet to the HTML file.
  • Add a CSS reset and base font styling.
Build the HTML structure

Your portfolio needs four distinct sections: a hero banner at the top, an about section, a project showcase, and a way for people to contact you. You'll build each one as a separate block of HTML.

  • In index.html, find the line that says <h1>Hello!</h1>.
  • Select that line and replace it with this code:
  <header class="hero">
    <h1>Your Name</h1>
    <p>Web Developer</p>
    <a href="#projects" class="btn">View My Work</a>
  </header>

  <section class="about" id="about">
    <h2>About Me</h2>
    <p>I'm a web developer who loves building clean, responsive websites. I'm passionate about learning new technologies and creating things that live on the internet.</p>
  </section>

What does this code do?

  • <header class="hero"> wraps the top banner of your page. The class="hero" gives it a name your CSS can target later.
  • <a href="#projects" class="btn"> creates a clickable link that jumps the visitor down to the projects section.
  • <section> is a semantic HTML element that groups related content together, making your page easier to read and style.
  • Save index.html.
  • Check your Live Preview panel. You should see "Your Name" as a large heading, "Web Developer" below it, a "View My Work" link, and the About Me paragraph.

Don't see the new content?

  • Make sure you deleted the original <h1>Hello!</h1> line. The new code should be the only content between your <body> tags.
  • Check that you saved the file. An unsaved file shows a dot on its tab in VS Code.

Still stuck? Help me figure out why my HTML isn't showing in the Live Preview panel.

Now you'll add the projects section. This is the centerpiece of your portfolio where you showcase your work.

  • In index.html, place your cursor at the end of the closing </section> tag (the one that closes the about section) and press Enter to start a new line.
  • Paste this code below the about section:
  <section class="projects" id="projects">
    <h2>My Projects</h2>
    <div class="project-grid">
      <div class="project-card">
        <h3>Project One</h3>
        <p>A brief description of this project and what I learned building it.</p>
      </div>
      <div class="project-card">
        <h3>Project Two</h3>
        <p>A brief description of this project and what I learned building it.</p>
      </div>
      <div class="project-card">
        <h3>Project Three</h3>
        <p>A brief description of this project and what I learned building it.</p>
      </div>
    </div>
  </section>

What does this code do?

  • <div class="project-grid"> is a container that will hold your cards in a row once you add CSS in a later step.
  • Each <div class="project-card"> is one project card with a title and short description.
  • The class names don't change anything visually yet. They're hooks your CSS will target to apply layout and styling.
  • Save index.html.
  • Check the Live Preview. You should now see three project headings (Project One, Project Two, Project Three) stacked below the about paragraph.

Only see part of your content?

  • Make sure you pasted the new code after the closing </section> of the about section, not inside it.
  • Check for missing closing tags. Every <div> needs a matching </div> and every <section> needs a matching </section>.

Need help? Help me figure out why my project cards aren't showing up in the preview.

The last two pieces are a contact section and a footer to close out the page.

  • Place your cursor at the end of the closing </section> tag (the one that closes the projects section) and press Enter.
  • Paste this code below the projects section:
  <section class="contact" id="contact">
    <h2>Get In Touch</h2>
    <p>Want to work together? Reach out!</p>
    <a href="mailto:your@email.com" class="btn">Email Me</a>
  </section>

  <footer>
    <p>Built by Your Name | 2026</p>
  </footer>

What does this code do?

  • <a href="mailto:your@email.com"> creates a link that opens the visitor's email app with your address pre-filled when clicked.
  • <footer> marks the bottom of the page. It sits outside of <section> tags because it's not a content section.
  • Save index.html.
  • Check the Live Preview. You should see all your content flowing from top to bottom: name, tagline, button, about text, three project titles, contact text, email button, and a footer line.

Footer not appearing?

  • Scroll down in the preview panel. The footer sits at the very bottom of the page.
  • Make sure the <footer> block is placed after the contact </section> closing tag, but still before the </body> tag.
Connect the stylesheet

Your style.css file exists in the same folder, but the HTML page doesn't know about it yet. A <link> tag in the <head> tells the browser to load and apply that stylesheet.

  • In index.html, find the <title>My Portfolio</title> line inside <head>.
  • Find this line:
  <title>My Portfolio</title>
  • Add a new line directly below it with this code:
  <link rel="stylesheet" href="style.css">
  • Save index.html.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file and the preview still shows all your content.

ⓧ I'd like to double check the full code

Scroll up through your index.html and confirm these sections appear in order between the <body> tags:

  • A <header class="hero"> with your name, tagline, and a "View My Work" link.
  • A <section class="about"> with a heading and paragraph.
  • A <section class="projects"> containing a .project-grid div with three .project-card divs inside.
  • A <section class="contact"> with a heading and email link.
  • A <footer> with your name and year.

Also confirm that <link rel="stylesheet" href="style.css"> appears in the <head> section, directly below the <title> tag.

Add a CSS reset and base font

Every browser applies its own default spacing to HTML elements. A CSS reset removes those defaults so your design starts from a clean, predictable slate.

  • Switch to style.css in your editor by clicking its tab.
  • Add this code to set up the reset and base font:
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
}

What does this code do?

  • The * selector targets every element on the page. Removing default margins and padding gives you a clean starting point.
  • box-sizing: border-box means padding and borders are counted inside an element's width, making layouts more predictable.
  • The body styles set a readable font and comfortable line spacing for all text on the page.

Before you check the preview, what do you think will change? The content is the same, but the spacing around it should shift.

  • Save style.css.
  • Check the Live Preview panel. Your text should now sit flush against the top-left corner of the page with tighter spacing between elements. The font has switched to Arial.

Text still has gaps around the edges?

  • Make sure the <link rel="stylesheet" href="style.css"> tag is in your index.html file's <head>. Without it, the browser never loads your CSS.
  • Confirm the filename is exactly style.css in both the link tag's href and your actual file. A mismatch (like styles.css vs style.css) silently breaks the connection.

Still stuck? Help me figure out why my CSS reset isn't applying to my HTML page.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file.

ⓧ I'd like to double check the full code

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
}

Your page has all its raw content in place and a clean CSS foundation. Next up, you'll transform this plain text into a visually striking hero section with colour, spacing, and a call-to-action button.

Style the Hero and Typography

Your page has all its content, but it looks like a plain text document. The hero section is the first thing anyone sees on your portfolio.

In this step, you'll define a colour palette using CSS variables, then style the hero banner and typography to give your page a polished, professional look.

In this step, get ready to:
  • Define CSS variables for your colour palette.
  • Style the hero section with a gradient background and button.
  • Apply consistent typography across all sections.
Define your colour palette with CSS variables

CSS variables let you define colours in one place and reuse them everywhere. If you want to change your entire colour scheme later, you only edit one block of code.

  • In style.css, add this :root block at the very top of the file, above the existing * reset:
:root {
  --primary: #4f46e5;
  --primary-dark: #3730a3;
  --text: #1e1b4b;
  --text-light: #555;
  --bg: #ffffff;
  --bg-light: #f8fafc;
  --card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

What are CSS variables?

The :root selector targets the entire document. Any variable defined here (using the -- prefix) is available everywhere in your CSS.

You reference these variables with var(--name) syntax. Change the value in one place and every element that uses it updates automatically.

Now you need to connect these variables to your body element so the page uses your colour palette.

  • In style.css, find the existing body block:
body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
}
  • Add two new lines inside the body block so it looks like this:
body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
}
  • Save style.css.
  • Check the Live Preview panel. Your text should now appear in a dark indigo colour instead of the browser's default black.

Text colour hasn't changed?

  • Make sure your :root block is above the * reset, not inside it.
  • Check that the body block has color: var(--text); with the exact variable name from your :root block.
  • Confirm you saved the file (look for the dot on the style.css tab in VS Code).

Why isn't my CSS variable working on the body text?

Now add the hero section styling to create your gradient banner.

  • Add the following CSS below your body block in style.css:
.hero {
  background: linear-gradient(135deg, #ede9fe 0%, #e0f2fe 100%);
  text-align: center;
  padding: 100px 20px;
}

.hero h1 {
  font-size: clamp(2rem, 5vw, 3.5rem);
  margin-bottom: 12px;
}

.hero p {
  font-size: 1.2rem;
  color: var(--text-light);
  margin-bottom: 24px;
}

What does this code do?

  • linear-gradient(135deg, ...) creates a diagonal colour fade from light purple to light blue behind your hero.
  • clamp(2rem, 5vw, 3.5rem) makes the heading responsive. It scales with the viewport width but never goes smaller than 2rem or larger than 3.5rem.
  • The margin-bottom values create breathing room between your name, tagline, and button.
  • Save style.css and check the Live Preview.
  • You should see a soft purple-to-blue gradient behind your name, with the heading appearing large and the tagline in a lighter grey.

No gradient background showing?

  • Check that your HTML has class="hero" on the <header> element. The CSS targets .hero which requires that class name.
  • Make sure there are no typos in linear-gradient. The parentheses and commas must match exactly.

Help me fix my hero gradient background.

Style the button and section layout

Your "View My Work" link needs to look like a clickable button. The .btn class will turn it into a rounded purple pill that lifts when hovered.

  • Add the following CSS below your .hero p block in style.css:
.btn {
  display: inline-block;
  padding: 12px 28px;
  background: var(--primary);
  color: white;
  text-decoration: none;
  border-radius: 50px;
  font-weight: 600;
  transition: background 0.2s, transform 0.2s;
}

.btn:hover {
  background: var(--primary-dark);
  transform: translateY(-2px);
}

What does this code do?

  • border-radius: 50px rounds the corners so much that the button becomes a pill shape.
  • transition makes the colour and position changes animate smoothly over 0.2 seconds instead of snapping instantly.
  • translateY(-2px) lifts the button 2 pixels upward on hover, giving a subtle "press me" effect.
  • Save style.css and check the Live Preview.
  • You should see a purple pill-shaped button below your tagline. Hover over it and watch it lift slightly and darken.

The button looks great, but the content sections below are jammed against the edges. You need some breathing room.

  • Add this rule below .btn:hover to give every section consistent padding and a max width:
section {
  padding: 60px 20px;
  max-width: 900px;
  margin: 0 auto;
}

What does this code do?

  • max-width: 900px stops your content from stretching too wide on large monitors.
  • margin: 0 auto centres the section horizontally within the page.
  • Save style.css and check the Live Preview.
  • Your About Me, Projects, and Contact sections should now have comfortable spacing and sit centred on the page.

Sections still stretching full width?

  • Make sure the selector is section (no dot, no hash). It targets every <section> element directly.
  • Check that margin: 0 auto has a space between 0 and auto. Without auto, centring won't work.

Why isn't my section centering on the page?

Style the section headings and paragraph text

The headings and body text still use the browser's default sizes. Applying your CSS variables to them pulls the entire page into one consistent colour palette.

  • Add the following CSS below your section rule in style.css:
section h2 {
  font-size: 1.8rem;
  margin-bottom: 16px;
  color: var(--text);
}

.about p {
  color: var(--text-light);
  max-width: 600px;
}

What does this code do?

  • section h2 targets every <h2> inside a section, giving them a uniform size and colour.
  • .about p uses var(--text-light) to keep the About paragraph in a softer grey, making it feel less heavy than headings.
  • max-width: 600px prevents the About paragraph from stretching too wide, which improves readability.

Before you save, what do you think will change in the preview? Think about which elements these rules target.

  • Save style.css and check the Live Preview.
  • Your section headings should appear slightly smaller and in dark indigo. The About paragraph should look softer and stop at a comfortable reading width.

About text still full width?

  • Check that your HTML has class="about" on the About section element. Without the class, .about p has nothing to target.
  • Make sure max-width: 600px is inside the .about p block, not in the section h2 block above it.

Help me figure out why my .about p styles aren't applying.

✔️ Awesome, I've got everything!

Great. Double check you've saved style.css and move on to the next step.

ⓧ I'd like to double check the full code

Here is the full style.css at this point. Compare it with yours to spot any differences.

:root {
  --primary: #4f46e5;
  --primary-dark: #3730a3;
  --text: #1e1b4b;
  --text-light: #555;
  --bg: #ffffff;
  --bg-light: #f8fafc;
  --card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
}
.hero {
  background: linear-gradient(135deg, #ede9fe 0%, #e0f2fe 100%);
  text-align: center;
  padding: 100px 20px;
}

.hero h1 {
  font-size: clamp(2rem, 5vw, 3.5rem);
  margin-bottom: 12px;
}

.hero p {
  font-size: 1.2rem;
  color: var(--text-light);
  margin-bottom: 24px;
}
.btn {
  display: inline-block;
  padding: 12px 28px;
  background: var(--primary);
  color: white;
  text-decoration: none;
  border-radius: 50px;
  font-weight: 600;
  transition: background 0.2s, transform 0.2s;
}

.btn:hover {
  background: var(--primary-dark);
  transform: translateY(-2px);
}
section {
  padding: 60px 20px;
  max-width: 900px;
  margin: 0 auto;
}

section h2 {
  font-size: 1.8rem;
  margin-bottom: 16px;
  color: var(--text);
}

.about p {
  color: var(--text-light);
  max-width: 600px;
}

Your hero section now has a polished gradient banner, responsive typography, and a button that responds to hover. Next up, you'll build the project card grid that will showcase your work.

Create the Projects Grid

Your hero section and typography look polished. Now it's time to tackle the centerpiece of your portfolio: the project cards.

Right now, your three project cards are stacking on top of each other as plain text. Flexbox makes fixing this simple. One CSS property turns a stack of divs into a horizontal row with equal spacing.

In this step, get ready to:
  • Arrange your project cards in a side-by-side row using Flexbox.
  • Add hover effects that lift cards when you mouse over them.
  • Style the contact section and footer to complete the page.
Lay out the project grid with Flexbox

Your HTML already has a .project-grid container wrapping three .project-card divs. You just need CSS to tell the browser to put them in a row instead of stacking them.

  • In style.css, scroll to the bottom of your file (below the .about p rule).
  • Add the grid layout and card base styling by pasting this code:
.project-grid {
  display: flex;
  gap: 20px;
}

.project-card {
  flex: 1;
  background: var(--bg-light);
  padding: 24px;
  border-radius: 12px;
  box-shadow: var(--card-shadow);
  transition: transform 0.2s;
}

What does this code do?

Two rules work together here to transform the card layout.

  • display: flex on the container tells the browser to place all children (the cards) in a horizontal row instead of stacking them vertically.
  • gap: 20px adds even spacing between each card without needing margins.
  • flex: 1 on each card tells it to grow and share the available space equally with its siblings.
  • The border-radius, box-shadow, and background give each card a polished appearance using the CSS variables you defined earlier.
  • Save style.css.
  • Check the Live Preview panel. Your three project cards should now sit side by side in a row, each with a light background, rounded corners, and a subtle shadow.

Cards still stacking vertically?

  • Make sure you saved style.css. Live Preview only updates on save.
  • Check that your new CSS is placed outside the closing brace of the .about p rule, not accidentally inside it.
  • Confirm that your HTML uses class="project-grid" on the container div. The CSS class name must match exactly.

Still stuck? Why aren't my project cards appearing side by side with Flexbox?

Add hover effects and card text styles

The cards are in a row, but they feel static. A hover effect gives the user feedback that the cards are interactive. You will also refine the card text so headings stand out and descriptions are visually quieter.

  • Add the hover and text styles directly below your .project-card rule by pasting this code:
.project-card:hover {
  transform: translateY(-4px);
}

.project-card h3 {
  margin-bottom: 8px;
  color: var(--primary);
}

.project-card p {
  color: var(--text-light);
  font-size: 0.9rem;
}

What does this code do?

  • transform: translateY(-4px) moves the card 4 pixels upward on hover, creating a "lift" effect. The transition: transform 0.2s you added in the previous chunk makes this movement smooth instead of instant.
  • The h3 inside each card uses your --primary colour variable, so project titles appear in purple.
  • The p text is set to a lighter colour and smaller font size so it doesn't compete with the heading.
  • Save style.css.
  • Hover your mouse over each card in the Live Preview. Each card should lift slightly upward with a smooth animation. The project titles should now appear in purple.

Hover not doing anything?

  • The Live Preview panel sometimes doesn't register hover effects. Try right-clicking the preview and selecting Open in Browser to test hover in Chrome instead.
  • Check that your .project-card rule from the previous chunk includes transition: transform 0.2s. Without it, the hover still works but happens instantly (hard to notice).

Need help? Why isn't my CSS hover effect working on the project cards?

Style the contact section and footer

The last two sections of your page still look plain. A background colour on the contact section separates it visually, and the footer gets minimal styling to stay out of the way.

  • Add the contact and footer styles below your card rules by pasting this code:
.contact {
  text-align: center;
  background: var(--bg-light);
  max-width: 100%;
  padding: 60px 20px;
}

.contact p {
  color: var(--text-light);
  margin-bottom: 20px;
}

footer {
  text-align: center;
  padding: 20px;
  color: var(--text-light);
  font-size: 0.85rem;
}

What does this code do?

  • .contact uses max-width: 100% to override the max-width: 900px set on all sections. This lets the background colour stretch edge to edge while the text stays centered.
  • The footer uses small, light text so it doesn't draw attention away from the main content above.
  • Save style.css.
  • Scroll down in the Live Preview. The contact section should now have a light background spanning the full width, with the "Email Me" button centered. The footer text should appear small and muted at the very bottom.

Contact background not spanning full width?

  • Make sure you included max-width: 100% in the .contact rule. Without it, the section inherits the max-width: 900px from the general section rule and the background won't reach the edges.
  • Check that your HTML uses class="contact" on the contact section element.

Need a hand? Why isn't my contact section background going full width?

Before you move on, take a moment to predict: what do you think will happen if you drag the preview panel to a very narrow width, simulating a phone screen?

Try it now. You will notice the three project cards get squeezed into unreadable slivers. That broken layout on narrow screens is exactly the problem you will fix in the next step.

✔️ Awesome, I've got everything!

Great. Double check you have saved style.css before moving on.

ⓧ I'd like to double check the full code

Your style.css file should contain these sections in order. Scroll through and confirm each rule is present.

  • :root variables (from Step 3).
  • * { ... } reset (from Step 2).
  • body { ... } base styles (from Step 2).
  • .hero, .hero h1, .hero p (from Step 3).
  • .btn and .btn:hover (from Step 3).
  • section and section h2 (from Step 3).
  • .about p (from Step 3).
  • .project-grid (this step).
  • .project-card, .project-card:hover, .project-card h3, .project-card p (this step).
  • .contact, .contact p, and footer (this step).

Here is the code you added in this step (the new rules at the bottom of your file):

.project-grid {
  display: flex;
  gap: 20px;
}

.project-card {
  flex: 1;
  background: var(--bg-light);
  padding: 24px;
  border-radius: 12px;
  box-shadow: var(--card-shadow);
  transition: transform 0.2s;
}

.project-card:hover {
  transform: translateY(-4px);
}

.project-card h3 {
  margin-bottom: 8px;
  color: var(--primary);
}

.project-card p {
  color: var(--text-light);
  font-size: 0.9rem;
}
.contact {
  text-align: center;
  background: var(--bg-light);
  max-width: 100%;
  padding: 60px 20px;
}

.contact p {
  color: var(--text-light);
  margin-bottom: 20px;
}

footer {
  text-align: center;
  padding: 20px;
  color: var(--text-light);
  font-size: 0.85rem;
}

Your project grid is the highlight of the page now. Next up, you will make it survive on small screens by adding a media query that stacks the cards vertically on mobile.

Make It Mobile-Friendly

Your portfolio looks polished at full width. The hero banner pops, the project cards sit neatly in a row, and the typography is clean.

But what happens when someone visits your site on a phone? Right now, those three side-by-side cards will get crushed into unreadable slivers. A media query tells the browser to switch layouts below a certain screen width, fixing the problem.

In this step, get ready to:
  • Observe how the current layout breaks on narrow screens.
  • Add a media query that stacks cards vertically on mobile.
  • Confirm the responsive behaviour works in both directions.
Resize the preview to see the problem

Before writing any new code, you need to see the problem with your own eyes.

  • In VS Code, grab the left edge of the Live Preview panel and drag it to the right until the preview area is about the width of a phone screen.

What do you think will happen to the three project cards?

The three cards are still forced side-by-side, squeezed into tiny unreadable columns. The text wraps awkwardly and the cards lose their shape entirely. This is the exact problem a visitor on a mobile device would hit.

Why does this happen?

Your .project-grid uses display: flex, which lays items out in a horizontal row by default. Flexbox will keep shrinking each card to fit them all in one line, no matter how narrow the screen gets.

You need a way to tell the browser: below a certain width, switch the cards from a row to a vertical stack.

Add a media query for mobile screens

A media query is a CSS rule that only activates when the screen matches a condition. You will add one that kicks in below 768 pixels wide.

  • In style.css, scroll to the very bottom of the file (after the footer rule).
  • Add the following media query at the end of the file:
@media (max-width: 768px) {
  .project-grid {
    flex-direction: column;
  }

  .hero {
    padding: 60px 20px;
  }
}

What does this code do?

  • @media (max-width: 768px) means everything inside this block only applies when the screen is 768 pixels wide or narrower.
  • flex-direction: column switches the project cards from a horizontal row to a vertical stack.
  • Reducing the hero padding from 100px to 60px prevents the banner from taking up the entire phone screen.
  • Save style.css.

Cards still side by side?

  • Make sure the media query is outside (after) the closing brace of the footer rule. Nesting it inside another rule will prevent it from working.
  • Check that you typed @media with the @ symbol. Without it, the browser ignores the rule entirely.
  • Confirm the preview panel is actually narrow enough. Drag it until it is clearly phone-sized.

Still stuck? Why aren't my project cards stacking on a narrow screen?

Confirm the responsive layout works both ways

A responsive design needs to work in both directions. It should stack on narrow screens and return to a row on wide ones.

  • With the preview panel still narrow, look at the project cards.

Do you think the cards are stacked vertically now, or still side by side?

The three cards now stack vertically, each taking the full width of the panel. The text is readable and the cards have room to breathe.

  • Now drag the preview panel back to full width.

The cards snap back into a horizontal row, sitting side by side just like before. Your layout responds to the available space.

What is responsive design?

Responsive design means your site adapts its layout to the screen it's viewed on. Instead of building separate mobile and desktop versions, you write one set of HTML and use media queries to adjust the CSS at different widths.

✔️ Awesome, I've got everything!

Great. Double check you have saved style.css and that the preview responds to resizing.

ⓧ I'd like to double check the full code

Your index.html should be unchanged from the previous step. In style.css, confirm these sections are present from top to bottom:

  • The :root CSS variables block.
  • The universal reset (* selector).
  • The body font and colour rules.
  • The .hero, .hero h1, and .hero p rules.
  • The .btn and .btn:hover rules.
  • The section, section h2, and .about p rules.
  • The .project-grid, .project-card, .project-card:hover, .project-card h3, and .project-card p rules.
  • The .contact, .contact p, and footer rules.
  • The @media (max-width: 768px) block at the very end, containing .project-grid and .hero overrides.

Here is the media query you just added. Confirm it matches the bottom of your file:

@media (max-width: 768px) {
  .project-grid {
    flex-direction: column;
  }

  .hero {
    padding: 60px 20px;
  }
}

Secret mission

Add a Dark Mode Toggle

Dark mode is a feature users expect on modern websites. In this bonus challenge, you'll add a toggle button that flips your entire colour scheme with a single click. You'll use CSS variables (the same ones you already defined) to make it work with surprisingly little code.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your files for future editing, or delete them entirely.

Resources you used:

  • The portfolio folder on your Desktop (containing index.html and style.css).
  • The Live Preview extension installed in VS Code.

Keep everything running

No action needed. Choose this if you plan to keep editing your portfolio or want to show it to someone later.

  • Your files stay in the portfolio folder on your Desktop, ready to open anytime.
  • You can open index.html directly in any browser to view your site without VS Code.
  • The Live Preview extension stays available in VS Code for future HTML projects.

Pause - I'll come back to this later

Since this project uses only local files, there is nothing to pause or shut down. Your files sit quietly on your Desktop until you open them again.

  • Close the VS Code window when you are done editing.
  • Your portfolio folder remains on your Desktop, taking up minimal space.

Delete - I don't want to use this again

Remove all project files and the extension you installed.

  • Delete the project folder. On macOS or Linux, open Terminal and run the command below. On Windows, open PowerShell and run: Remove-Item -Recurse -Force ~/Desktop/portfolio.
  • Uninstall the Live Preview extension by pressing Ctrl+Shift+X to open the Extensions panel, finding Live Preview in the list, clicking the gear icon next to it, and selecting Uninstall.
rm -rf ~/Desktop/portfolio

After running this, confirm the folder is gone by running:

ls ~/Desktop | grep portfolio

You should see no output, confirming the folder has been removed.

Nice Work!

Nice Work!

You just built a personal portfolio website from scratch using nothing but HTML and CSS. You can open it in any browser and show people what you made.

You've learned how to:

  • Structure a complete webpage with semantic HTML and style it with CSS variables so your entire colour theme lives in one place.
  • Lay out project cards in a horizontal row using Flexbox and add hover transitions that make the cards lift on interaction.
  • Make a desktop layout responsive with a single media query so the page stacks cleanly on mobile screens.
  • Secret Mission: Added a dark mode toggle that flips the entire page theme by overriding CSS variables with one line of JavaScript.

Ready to quiz yourself?