Build an Interactive Profile Page

Build a responsive personal profile page with HTML, CSS, and JavaScript.

Introduction

30 Second Summary

A personal profile often has only a few seconds to show visitors who you are. An awkward mobile layout can hide the work you want them to notice.

In this project, you will build a responsive personal profile page with semantic HTML, CSS, and JavaScript. You will connect a keyboard-friendly button that switches the page between light and dark themes.

What You'll Build

Opening index.html in Microsoft Edge gives you a polished profile that reshapes itself for mobile screens and switches color themes from one accessible button.

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

  • Present a meaningful profile with an introduction, skill chips, project cards, and internal page links.
  • Demo a responsive layout that moves from a desktop grid to a clean mobile column without horizontal scrolling.
  • Switch between light and dark themes with a mouse or keyboard while the button's accessibility state stays synchronized.
  • Secret Mission: Add filter buttons that show only the project cards matching HTML, CSS, or JavaScript.

Are there any prerequisites?

No previous web development experience is required. The guide includes setup for Visual Studio Code and Microsoft Edge on your Windows computer.

Before We Start

Before any hands-on work begins, this is your moment to commit to the profile you are creating for Alex Morgan. A clear goal keeps every later choice focused on presenting responsive frontend skills professionally.

Set Up Your Windows Workspace

A reliable editor and browser remove setup friction before you write your first real page.

Visual Studio Code gives you one place to manage your project files. Microsoft Edge shows you exactly what those files produce.

A dedicated folder keeps the page structure, styling, and behavior together. This foundation gives every future change a clear home.

In this step, get ready to:
  • Prepare Microsoft Edge for local testing.
  • Prepare Visual Studio Code for editing.
  • Build a three-file workspace with a visible starter heading.
Prepare Microsoft Edge

Microsoft Edge is the browser where you test each visible change. Windows search gives you a quick way to confirm whether it is ready.

  • Press the Windows key to open Start.
  • Type Microsoft Edge in the search box.
  • Press Enter to open the search result.

✔️ Microsoft Edge opens

Microsoft Edge is ready to display the local profile page you create later in this step.

ⓧ Microsoft Edge is missing

  • Visit the Microsoft Edge download page in any available browser.
  • Download the Windows installer from the official page.
  • Run the downloaded installer from your Downloads folder.
  • Follow the on-screen instructions until installation finishes.
  • Press the Windows key to open Start.
  • Type Microsoft Edge in the search box.
  • Press Enter to open Microsoft Edge.

The open browser window confirms that Microsoft Edge is ready.

Microsoft Edge Still Won't Open?

Restart Windows if the newly installed browser does not appear in search. This gives Windows a fresh chance to register the app.

Still stuck? Help me troubleshoot Microsoft Edge on Windows.

Prepare Visual Studio Code

Visual Studio Code is free to use. Its User Setup installer works without administrator permissions for most Windows learners.

  • Press the Windows key to open Start.
  • Type Visual Studio Code in the search box.
  • Press Enter to open the search result.

✔️ Visual Studio Code opens

Visual Studio Code is ready when you can see its editor window.

ⓧ Visual Studio Code is missing

  • Visit the Visual Studio Code Windows setup guide in Microsoft Edge.
  • Download the User Setup installer for Windows.
  • Run the downloaded .exe file.
  • Follow the installer prompts until setup finishes.
  • Press the Windows key to open Start.
  • Type Visual Studio Code in the search box.
  • Press Enter to open Visual Studio Code.

The open editor window confirms that Visual Studio Code is ready.

Visual Studio Code Still Won't Open?

Run the User Setup installer again if setup closed before completion. Restart Windows if the installed app still does not appear in search.

Need another pair of eyes? Help me troubleshoot my Visual Studio Code installation.

Create and test the starter page

A workspace is a folder that Visual Studio Code treats as one project. HTML provides the page structure.

CSS handles presentation. JavaScript adds behavior.

  • Switch back to Visual Studio Code from earlier.
  • Click File in the top menu.
  • Select Open Folder....
  • Select your Desktop in the folder dialog.

You are now choosing the exact location for your project folder.

  • Click New Folder.
  • Type profile-page as the folder name.
  • Press Enter.
  • Select the profile-page folder.
  • Click Select Folder.

Why Open a Workspace?

Opening profile-page as a workspace places every project file in the Explorer view. This keeps navigation focused on one project.

Visual Studio Code may show Workspace Trust after opening the folder. You created this folder yourself, so you can trust its contents.

  • Select Yes, I trust the authors if Workspace Trust appears.

That setup hurdle is cleared. Your profile-page folder now has a dedicated editing window.

  • Click New File... in the Explorer view.
  • Type index.html.
  • Press Enter.

You should see index.html in the Explorer view.

  • Click New File... in the Explorer view.
  • Type styles.css.
  • Press Enter.

You should now see the empty styles.css file beside index.html.

  • Click New File... in the Explorer view.
  • Type script.js.
  • Press Enter.

The Explorer view now lists index.html, styles.css, and script.js inside profile-page.

  • Select index.html in the Explorer view.
  • Create the starter page by replacing the empty editor with this code:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Profile Page</title>
  </head>
  <body>
    <h1>Frontend starts here</h1>
  </body>
</html>

What Does This Starter Page Do?

  • The <!doctype html> line identifies the file as an HTML document.
  • The head stores information about the page.
  • The body contains the heading that Microsoft Edge displays.
  • Save index.html by pressing Ctrl+S.

Before you open the file, what heading do you expect Microsoft Edge to display?

  • Right-click index.html in the Explorer view.
  • Select Reveal in File Explorer.
  • Double-click index.html in File Explorer.

That's your first visible win: Microsoft Edge is rendering the Frontend starts here heading from your own index.html file.

Don't See the Heading in Microsoft Edge?

  • Confirm that index.html is saved in Visual Studio Code.
  • Right-click index.html in File Explorer if another app opened.
  • Select Open with.
  • Select Microsoft Edge.

Still seeing a blank page? Help me check my starter HTML file.

✔️ Awesome, I've got everything!

Your starter page is saved. Keep Microsoft Edge available for the next step.

ⓧ I'd like to double check the full code

  • Compare your saved index.html file with this complete starter version:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Profile Page</title>
  </head>
  <body>
    <h1>Frontend starts here</h1>
  </body>
</html>

Your styles.css file should be empty. Your script.js file should also be empty.

Your Windows workspace is ready. Next, you'll replace the starter heading with the complete profile structure.

Build the Semantic Profile Page

Your Windows workspace is ready. The starter heading already opens in Microsoft Edge.

A browser needs structured content before it can present a meaningful profile. Semantic HTML creates that reading order while giving later CSS and JavaScript clear elements to work with.

In this step, get ready to:
  • Replace the starter heading with a complete document foundation.
  • Add the profile hero, skills list, and three project cards.
  • Verify the unstyled profile and its internal project link in Microsoft Edge.
Build the page foundation

The document foundation defines the page title, responsive viewport, header, main content area, and footer. It also connects the empty stylesheet and script that later steps use.

  • Switch back to index.html in Visual Studio Code.
  • Select all existing code with Ctrl+A.
  • Replace the selected code by pasting the document below:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Alex Morgan | Frontend Learner</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body data-theme="light">
    <header class="site-header">
      <a class="brand" href="#home">AM</a>
      <button id="theme-toggle" class="theme-toggle" type="button" aria-pressed="false">Toggle color theme</button>
    </header>
    <main id="home" class="page-shell">
      <section class="hero" aria-labelledby="profile-title">
        <div class="avatar" aria-hidden="true">AM</div>
        <div>
          <p class="eyebrow">Frontend learner</p>
          <h1 id="profile-title">Hi, I'm Alex Morgan.</h1>
          <p class="intro">I build clear, responsive interfaces while learning how HTML, CSS, and JavaScript work together in the browser.</p>
          <a class="primary-link" href="#projects">View my projects</a>
        </div>
      </section>
    </main>
    <footer class="site-footer"><p>Built with HTML, CSS, and JavaScript.</p></footer>
    <script src="script.js"></script>
  </body>
</html>

What Does This Foundation Do?

  • The viewport metadata makes the page width follow the device width.
  • The header contains the brand link and native theme button.
  • The main element contains the profile introduction.
  • The styles.css and script.js references connect the empty files from your project folder.
  • Save index.html with Ctrl+S.
  • Switch back to Microsoft Edge.
  • Refresh the page with Ctrl+R.

Good progress. You should now see the initials, theme button, profile introduction, project link, and footer.

Page Not Loading Correctly?

  • Check that every opening HTML tag has a matching closing tag.
  • Confirm that styles.css and script.js still use the exact file names shown in the Explorer sidebar.

Still stuck? Help me compare my profile page foundation with the expected HTML.

Add the skills and projects sections

A skills list groups related abilities under one heading. A dedicated section makes that group easy to find in the page's reading order.

  • In index.html, find the closing </section> immediately before </main>.
  • Place your cursor on a new line after that closing tag.
  • Insert the skills panel by pasting the code below:
      <section class="panel" aria-labelledby="skills-title">
        <h2 id="skills-title">Skills I'm building</h2>
        <ul class="chip-list"><li>Semantic HTML</li><li>Responsive CSS</li><li>JavaScript</li><li>Accessibility</li></ul>
      </section>

What Does the Skills Section Do?

  • The section groups four related skills into one meaningful part of the page.
  • The aria-labelledby value connects the section to its visible heading.
  • The unordered list preserves the relationship between the four skill items.
  • Save index.html with Ctrl+S.
  • Switch back to Microsoft Edge.
  • Refresh the page with Ctrl+R.

Your profile now includes a Skills I'm building heading. You should see four skills listed below it.

Skills List Missing?

  • Confirm that the skills section sits inside main.
  • Check that the ul element has a closing </ul> tag.

Need a second pair of eyes? Help me find why my skills list is missing.

Each project belongs in its own article element because each card represents a complete item. The shared project-card class gives all three cards one styling target for the next step.

  • Find the closing </section> immediately before </main>.
  • Place your cursor on a new line after that closing tag.
  • Insert the projects section by pasting the code below:
      <section id="projects" class="panel" aria-labelledby="projects-title">
        <h2 id="projects-title">Practice projects</h2>
        <div class="project-grid">
          <article class="project-card"><p class="project-number">Project 01</p><h3>Recipe Landing Page</h3><p>A structured recipe page with clear sections and readable content.</p></article>
          <article class="project-card"><p class="project-number">Project 02</p><h3>Responsive Event Card</h3><p>A compact event interface that adapts from desktop to mobile.</p></article>
          <article class="project-card"><p class="project-number">Project 03</p><h3>Theme Switcher</h3><p>An interactive control that changes a page's color theme.</p></article>
        </div>
      </section>

What Does the Projects Section Do?

  • The projects identifier gives the View my projects link a destination.
  • The project-grid container groups the cards for the desktop layout.
  • Each project-card article keeps its project number, title, and description together.
  • Save index.html with Ctrl+S.
  • Switch back to Microsoft Edge.
  • Refresh the page with Ctrl+R.

That completes the visible content. You should now see three project titles with a description under each one.

Project Cards Not Appearing?

  • Confirm that the projects section appears before the closing </main> tag.
  • Check that each article element ends with </article>.

Still missing a card? Help me check my project article structure.

✔️ Awesome, I've got everything!

Your semantic content is complete. The browser check below confirms the final reading order.

ⓧ I'd like to double check the full code

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Alex Morgan | Frontend Learner</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body data-theme="light">
    <header class="site-header">
      <a class="brand" href="#home">AM</a>
      <button id="theme-toggle" class="theme-toggle" type="button" aria-pressed="false">Toggle color theme</button>
    </header>
    <main id="home" class="page-shell">
      <section class="hero" aria-labelledby="profile-title">
        <div class="avatar" aria-hidden="true">AM</div>
        <div>
          <p class="eyebrow">Frontend learner</p>
          <h1 id="profile-title">Hi, I'm Alex Morgan.</h1>
          <p class="intro">I build clear, responsive interfaces while learning how HTML, CSS, and JavaScript work together in the browser.</p>
          <a class="primary-link" href="#projects">View my projects</a>
        </div>
      </section>
      <section class="panel" aria-labelledby="skills-title">
        <h2 id="skills-title">Skills I'm building</h2>
        <ul class="chip-list"><li>Semantic HTML</li><li>Responsive CSS</li><li>JavaScript</li><li>Accessibility</li></ul>
      </section>
      <section id="projects" class="panel" aria-labelledby="projects-title">
        <h2 id="projects-title">Practice projects</h2>
        <div class="project-grid">
          <article class="project-card"><p class="project-number">Project 01</p><h3>Recipe Landing Page</h3><p>A structured recipe page with clear sections and readable content.</p></article>
          <article class="project-card"><p class="project-number">Project 02</p><h3>Responsive Event Card</h3><p>A compact event interface that adapts from desktop to mobile.</p></article>
          <article class="project-card"><p class="project-number">Project 03</p><h3>Theme Switcher</h3><p>An interactive control that changes a page's color theme.</p></article>
        </div>
      </section>
    </main>
    <footer class="site-footer"><p>Built with HTML, CSS, and JavaScript.</p></footer>
    <script src="script.js"></script>
  </body>
</html>

How Does the Document Fit Together?

  • The header gives visitors a brand link and a theme control.
  • The main area presents the hero, skills, and projects in a logical order.
  • The footer closes the visible page before the browser loads script.js.
Verify the complete profile

Before you refresh, what content do you expect to see between the header and footer?

  • Switch back to Microsoft Edge.
  • Refresh index.html with Ctrl+R.

You should see the profile introduction followed by the skills list. Below it, you should see three project cards followed by the footer.

At the top, you should see the initials and the Toggle color theme button. The content looks plain because styles.css is still empty.

  • Click View my projects.

Microsoft Edge moves directly to the Practice projects section. That confirms the internal link reaches the projects section.

Internal Link Not Moving?

  • Confirm that the link uses href="#projects".
  • Confirm that the projects section uses id="projects".

Need help matching them? Help me fix my internal projects link.

Your profile now has a meaningful structure that works before any styling loads. Next, you'll turn this readable page into a polished desktop design.

Design the Desktop Experience

Your semantic HTML now presents the profile in a logical order. Every section still looks like plain document content.

Visual hierarchy helps visitors understand what matters first. In this step, you'll use CSS to turn the raw page into a polished desktop interface.

A fixed desktop canvas gives the design clear proportions. It also gives you a specific layout to pressure-test on a narrow screen.

In this step, get ready to:
  • Create the visual foundation with CSS custom properties and global page rules.
  • Arrange the profile sections with CSS Grid and a fixed desktop width.
  • Pressure-test the canvas with Microsoft Edge DevTools Device Emulation at 400 pixels.
Define the design foundations

CSS custom properties keep the page colors in one central palette. A predictable box model keeps padding and borders inside each element's declared dimensions.

  • Switch back to the empty styles.css file in Visual Studio Code.
  • Paste the following CSS into styles.css:
:root{--background:#f4f7fb;--surface:#fff;--text:#18212f;--muted:#5f6b7a;--accent:#5b4bff;--accent-contrast:#fff;--border:#dce3ec;--shadow:0 18px 45px rgba(24,33,47,.12)}*,*::before,*::after{box-sizing:border-box}html{scroll-behavior:smooth}body{min-height:100vh;margin:0;background:var(--background);color:var(--text);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6}a{color:inherit}button{font:inherit}

What Do These Foundations Do?

  • The :root selector stores the page's color palette and shadow as reusable custom properties.
  • The box-sizing rule makes element dimensions easier to control.
  • The body rule applies the page background, text color, font stack, and line spacing.
  • The a and button rules make links and buttons inherit the surrounding design.
  • Save styles.css.
  • Switch back to the profile page in Microsoft Edge.
  • Refresh the profile page.

You'll see a pale background with dark text. The page also uses cleaner typography and more comfortable line spacing.

Page Still Looks Unstyled?

Confirm that styles.css is inside the profile-page folder beside index.html.

Check that the browser is displaying the index.html file from that same folder.

Still seeing the plain page? Help me find why styles.css is not affecting my profile page.

The global palette now gives every component a shared visual language. The next selectors use that palette to shape the header, brand mark, avatar, and theme control.

  • Append the following CSS immediately after the button rule in styles.css:
.site-header{display:flex;align-items:center;justify-content:space-between;padding:1.25rem 2rem;border-bottom:1px solid var(--border);background:var(--surface)}.brand,.avatar{display:grid;place-items:center;background:var(--accent);color:var(--accent-contrast);font-weight:800}.brand{width:2.75rem;height:2.75rem;border-radius:50%;text-decoration:none}.theme-toggle{padding:.65rem 1rem;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:var(--text);cursor:pointer}

How Do These Controls Take Shape?

  • The .site-header selector places the brand and theme button on opposite sides of one row.
  • The shared .brand, .avatar rule centers their initials and applies the accent palette.
  • The .brand selector creates the circular logo at the top of the page.
  • The .theme-toggle selector turns the native button into a rounded control.
  • Save styles.css.
  • Switch back to Microsoft Edge.
  • Refresh the profile page.

You'll see a white header with a round purple brand mark. The theme button now looks like a pill-shaped control.

Header Still Looks Plain?

Check that the new CSS begins immediately after the closing brace of the button rule.

Confirm that .site-header, .brand, .avatar, and .theme-toggle match the classes in index.html.

Need help with the header? Help me debug why my header CSS is not appearing.

Build the desktop layout

The remaining selectors establish spacing and visual hierarchy across the page. The fixed .page-shell gives the desktop design a width of 920px.

  • Append the following CSS immediately after the .theme-toggle rule in styles.css:
.page-shell{width:920px;margin:0 auto;padding:4rem 0}.hero{display:grid;grid-template-columns:220px 1fr;align-items:center;gap:3rem;margin-bottom:2rem}.avatar{width:220px;aspect-ratio:1;border-radius:2rem;box-shadow:var(--shadow);font-size:3rem}.eyebrow,.project-number{margin:0 0 .5rem;color:var(--accent);font-size:.8rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase}h1,h2,h3,p{margin-top:0}h1{margin-bottom:1rem;font-size:2.75rem;line-height:1.1}.intro,.project-card p:last-child{color:var(--muted)}.intro{max-width:60ch;font-size:1.1rem}.primary-link{display:inline-block;padding:.75rem 1rem;border-radius:.75rem;background:var(--accent);color:var(--accent-contrast);font-weight:700;text-decoration:none}.panel{padding:1.75rem;border:1px solid var(--border);border-radius:1.25rem;background:var(--surface);box-shadow:var(--shadow)}.panel+.panel{margin-top:1.5rem}.chip-list{display:flex;flex-wrap:wrap;gap:.75rem;padding:0;margin:0;list-style:none}.chip-list li{padding:.45rem .8rem;border:1px solid var(--border);border-radius:999px;color:var(--muted)}.project-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.project-card{padding:1.25rem;border:1px solid var(--border);border-radius:1rem}.site-footer{padding:1.5rem;border-top:1px solid var(--border);color:var(--muted);text-align:center}.site-footer p{margin:0}

How Does the Desktop Layout Work?

  • The .page-shell selector centers the content inside a fixed desktop canvas.
  • The .hero selector creates one column for the avatar and another for the introduction.
  • The .panel rules give the skills and projects sections a shared card treatment.
  • The .chip-list selector turns the skills into wrapped pill shapes.
  • The .project-grid selector arranges project cards across two columns.
  • Save styles.css.
  • Switch back to Microsoft Edge.
  • Refresh the profile page.

You'll see a centered profile with a two-column hero, a large purple avatar, skill chips, bordered panels, and a project grid.

You made the structure feel like a real interface. Each part of the profile now has a distinct visual role.

Desktop Sections Not Lining Up?

Confirm that this CSS begins immediately after the closing brace of the .theme-toggle rule.

Check every opening brace in the new CSS for a matching closing brace.

Still seeing a broken layout? Help me compare my desktop CSS with the expected selectors.

✔️ Awesome, I've got everything!

Your styles.css file now contains the complete desktop design.

ⓧ I'd like to double check the full code

:root{--background:#f4f7fb;--surface:#fff;--text:#18212f;--muted:#5f6b7a;--accent:#5b4bff;--accent-contrast:#fff;--border:#dce3ec;--shadow:0 18px 45px rgba(24,33,47,.12)}*,*::before,*::after{box-sizing:border-box}html{scroll-behavior:smooth}body{min-height:100vh;margin:0;background:var(--background);color:var(--text);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6}a{color:inherit}button{font:inherit}.site-header{display:flex;align-items:center;justify-content:space-between;padding:1.25rem 2rem;border-bottom:1px solid var(--border);background:var(--surface)}.brand,.avatar{display:grid;place-items:center;background:var(--accent);color:var(--accent-contrast);font-weight:800}.brand{width:2.75rem;height:2.75rem;border-radius:50%;text-decoration:none}.theme-toggle{padding:.65rem 1rem;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:var(--text);cursor:pointer}.page-shell{width:920px;margin:0 auto;padding:4rem 0}.hero{display:grid;grid-template-columns:220px 1fr;align-items:center;gap:3rem;margin-bottom:2rem}.avatar{width:220px;aspect-ratio:1;border-radius:2rem;box-shadow:var(--shadow);font-size:3rem}.eyebrow,.project-number{margin:0 0 .5rem;color:var(--accent);font-size:.8rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase}h1,h2,h3,p{margin-top:0}h1{margin-bottom:1rem;font-size:2.75rem;line-height:1.1}.intro,.project-card p:last-child{color:var(--muted)}.intro{max-width:60ch;font-size:1.1rem}.primary-link{display:inline-block;padding:.75rem 1rem;border-radius:.75rem;background:var(--accent);color:var(--accent-contrast);font-weight:700;text-decoration:none}.panel{padding:1.75rem;border:1px solid var(--border);border-radius:1.25rem;background:var(--surface);box-shadow:var(--shadow)}.panel+.panel{margin-top:1.5rem}.chip-list{display:flex;flex-wrap:wrap;gap:.75rem;padding:0;margin:0;list-style:none}.chip-list li{padding:.45rem .8rem;border:1px solid var(--border);border-radius:999px;color:var(--muted)}.project-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.project-card{padding:1.25rem;border:1px solid var(--border);border-radius:1rem}.site-footer{padding:1.5rem;border-top:1px solid var(--border);color:var(--muted);text-align:center}.site-footer p{margin:0}
Expose the narrow-screen problem

A desktop design proves how the page works with plenty of space. A narrow viewport tests whether those dimensions can adapt when that space disappears.

  • Switch back to the profile page in Microsoft Edge.
  • Press F12 to open DevTools.
  • Click Toggle device emulation in the Activity Bar.

You'll see the Device Toolbar in Responsive Viewport Mode.

Before you change the width, predict whether the fixed 920px canvas will fit inside a 400 pixel viewport.

  • Enter 400 as the viewport width.

At 400 pixels, you'll see a horizontal scrollbar. Part of the desktop layout is clipped beyond the viewport.

Why Is the Overflow Intentional?

The .page-shell remains 920px wide while the viewport is only 400 pixels wide. The browser preserves the fixed canvas width.

That visible mismatch proves the narrow-screen problem exists. You now have a concrete failure to repair.

No Horizontal Scrollbar?

Confirm that the viewport width shows 400 in the Device Toolbar.

Refresh the page while Device Emulation remains active.

Still seeing the full desktop canvas? Help me check why the fixed-width page is not overflowing at 400 pixels.

Your polished desktop design now has a visible width mismatch at 400 pixels. Next, you'll replace the rigid dimensions with a layout that stacks cleanly on narrow screens.

Repair the Mobile Layout

Your polished desktop profile now has clear visual hierarchy. However, its fixed 920px canvas spills beyond the 400-pixel viewport in Microsoft Edge DevTools.

A responsive media query applies a different set of layout rules when the viewport reaches a chosen width. This lets the same page switch from a wide desktop grid to a narrow stacked layout.

In this step, get ready to:
  • Add a narrow-screen media query to styles.css.
  • Replace the fixed page width with a flexible layout below 960px.
  • Test the profile on both sides of the responsive breakpoint.
Add the responsive breakpoint

The existing desktop rules remain active above 960px. The new rules override only the dimensions that cause trouble on narrower screens.

  • In styles.css, place your cursor after the final closing brace:
@media (max-width:960px){.site-header{padding:1rem}.page-shell{width:auto;margin:0;padding:2.5rem 1rem}.hero{grid-template-columns:1fr;gap:1.5rem}.avatar{width:120px;border-radius:1.25rem;font-size:2rem}h1{font-size:2.1rem}.project-grid{grid-template-columns:1fr}}

What Does This CSS Do?

  • The @media (max-width:960px) condition activates these overrides at viewport widths of 960px or less.
  • The .page-shell selector replaces the fixed width with automatic sizing.
  • The .hero selector places the avatar above the introduction.
  • The .project-grid selector stacks every project card in one column.
  • The .avatar selector reduces the profile graphic to 120px wide.

Before you refresh, do you expect the fixed-width overflow to remain? Hold that prediction while you check.

  • Save styles.css.
  • Switch back to the profile page in Microsoft Edge.
  • Refresh the page at the current 400-pixel viewport.

You should see the page fit inside the viewport without a horizontal scrollbar. The avatar now sits above the introduction.

That overflow is fixed. Your desktop design now adapts to a narrow screen instead of forcing the screen to fit the desktop canvas.

Still Seeing Horizontal Overflow?

Confirm that you saved styles.css. Make sure the media query sits after the desktop styles so its rules take priority.

Check that the viewport width is set to 400. A wider viewport may keep the desktop layout active.

Still stuck? Help me find why my profile page still overflows at a 400-pixel viewport.

✔️ Awesome, I've got everything!

Your responsive rules are saved in styles.css.

ⓧ I'd like to double check the full code

:root{--background:#f4f7fb;--surface:#fff;--text:#18212f;--muted:#5f6b7a;--accent:#5b4bff;--accent-contrast:#fff;--border:#dce3ec;--shadow:0 18px 45px rgba(24,33,47,.12)}*{box-sizing:border-box}body{margin:0;background:var(--background);color:var(--text);font-family:system-ui,sans-serif;line-height:1.6}.site-header{display:flex;justify-content:space-between;padding:1.25rem 2rem;border-bottom:1px solid var(--border);background:var(--surface)}.brand,.avatar{display:grid;place-items:center;background:var(--accent);color:var(--accent-contrast);font-weight:800}.brand{width:2.75rem;height:2.75rem;border-radius:50%;text-decoration:none}.theme-toggle{padding:.65rem 1rem;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:var(--text)}.page-shell{width:920px;margin:0 auto;padding:4rem 0}.hero{display:grid;grid-template-columns:220px 1fr;gap:3rem;margin-bottom:2rem}.avatar{width:220px;aspect-ratio:1;border-radius:2rem;font-size:3rem}.panel{padding:1.75rem;border:1px solid var(--border);border-radius:1.25rem;background:var(--surface);box-shadow:var(--shadow)}.panel+.panel{margin-top:1.5rem}.chip-list{display:flex;flex-wrap:wrap;gap:.75rem;padding:0;list-style:none}.project-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.project-card{padding:1.25rem;border:1px solid var(--border);border-radius:1rem}.site-footer{text-align:center;color:var(--muted)}
@media (max-width:960px){.site-header{padding:1rem}.page-shell{width:auto;margin:0;padding:2.5rem 1rem}.hero{grid-template-columns:1fr;gap:1.5rem}.avatar{width:120px;border-radius:1.25rem;font-size:2rem}h1{font-size:2.1rem}.project-grid{grid-template-columns:1fr}}
Inspect the stacked layout

A successful mobile layout keeps every section readable inside the viewport. The content order stays unchanged while the grid presentation becomes vertical.

  • Scroll from the header to the footer at the 400-pixel viewport.
  • Confirm that the page shell leaves space along both sides of the screen.
  • Confirm that the profile introduction sits below the smaller avatar.
  • Confirm that all three project cards form one column.
  • Confirm that every link and button remains visible.

You should be able to read the entire profile from top to bottom without dragging the page sideways.

Test both sides of the breakpoint

The breakpoint creates two layout states. Widths above 960px use the desktop grid while widths at or below it use the stacked layout.

Before you resize, which layout do you expect to see at 961 pixels? Hold that prediction while you test it.

  • Set the emulated viewport width to 961 pixels.
  • Refresh the profile page.

You should see the wide desktop layout return. The hero uses two columns again.

Before the final check, do you expect the page to stack again at 400 pixels? Keep that prediction in mind.

  • Set the emulated viewport width back to 400 pixels.
  • Refresh the profile page.

You should see no horizontal scrollbar. The hero and project cards form one column while every control remains visible.

Your profile now moves cleanly between desktop and mobile layouts. Next, you will connect the theme button to JavaScript so visitors can control the page colors.

Make the Theme Button Work

Your profile now keeps its polished layout at desktop widths. Every section also remains visible at a 400-pixel viewport.

The theme button still has no behavior. A visitor can press it without changing the page.

In this step, JavaScript uses DOM selection to find the button.

A click event updates a data attribute on the page.

CSS custom properties turn that state change into a new color palette.

In this step, get ready to:
  • Add a dark palette with CSS custom properties.
  • Connect the theme button to the page state.
  • Verify mouse activation plus keyboard activation.
Add the dark theme colors

A dark-theme selector gives the existing custom properties a second set of values. Every rule using those properties reads the dark palette when the page state changes.

  • Switch back to the styles.css tab in Visual Studio Code.
  • Find the closing brace of the existing :root rule.
  • Add the dark-theme selector immediately after that rule by pasting this code:
body[data-theme="dark"]{--background:#10131a;--surface:#1a202b;--text:#f5f7fb;--muted:#b5bfcc;--accent:#9a8cff;--accent-contrast:#10131a;--border:#303949;--shadow:0 18px 45px rgba(0,0,0,.28)}

What does this CSS do?

  • The body[data-theme="dark"] selector activates only when the body carries the dark theme value.
  • Each custom property supplies a dark replacement for an existing page color.
  • The rest of the stylesheet keeps reading the same properties through var().
  • Save styles.css.
  • Switch back to your page in Microsoft Edge.
  • Right-click an empty part of the page.
  • Select Inspect.

Editing a live attribute in DevTools can feel fiddly the first time. The change stays inside the current browser page.

  • Find <body data-theme="light"> in Microsoft Edge DevTools.
  • Replace light with dark in the inspected element.

You'll see the page switch to a dark background with lighter text. This proves the new selector responds to the body's theme state.

  • Restore dark to light in the inspected element.

The original light palette returns. Your page now has two working color palettes.

A short color transition makes each theme change easier to follow. The body rule controls that motion for the page background plus its text.

  • Switch back to styles.css.
  • Replace the existing body rule with this version:
body{margin:0;background:var(--background);color:var(--text);font-family:system-ui,sans-serif;line-height:1.6;transition:background-color .2s ease,color .2s ease}

Why add a transition?

The transition animates changes to the background color plus the text color over a brief interval. The palette shift now feels connected to the button activation.

  • Save styles.css.
  • Return to the inspected body element in Microsoft Edge.
  • Change light to dark.

You'll see the background plus text fade into the dark palette.

  • Restore the inspected value to light.

The page fades back to its starting palette.

The button also needs visible pointer feedback. A pointer cursor signals that the control accepts a click.

  • Switch back to styles.css.
  • Replace the existing .theme-toggle rule with this version:
.theme-toggle{padding:.65rem 1rem;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:var(--text);cursor:pointer}

What does the cursor change?

The cursor declaration displays a pointer when the mouse moves over the theme button. That feedback identifies the button as interactive.

  • Save styles.css.
  • Return to the page in Microsoft Edge.
  • Move your mouse over the Toggle color theme button.

You'll see the cursor change to a pointer over the button.

Colors not changing?

Confirm the dark selector is body[data-theme="dark"] without a space before the attribute.

Check that each custom property name matches its light-theme version exactly.

Still stuck? Help me troubleshoot my dark-theme CSS.

✔️ Awesome, I've got everything!

Your stylesheet now contains both color palettes plus the visual feedback for switching between them.

ⓧ I'd like to double check the full code

:root{--background:#f4f7fb;--surface:#fff;--text:#18212f;--muted:#5f6b7a;--accent:#5b4bff;--accent-contrast:#fff;--border:#dce3ec;--shadow:0 18px 45px rgba(24,33,47,.12)}body[data-theme="dark"]{--background:#10131a;--surface:#1a202b;--text:#f5f7fb;--muted:#b5bfcc;--accent:#9a8cff;--accent-contrast:#10131a;--border:#303949;--shadow:0 18px 45px rgba(0,0,0,.28)}*{box-sizing:border-box}body{margin:0;background:var(--background);color:var(--text);font-family:system-ui,sans-serif;line-height:1.6;transition:background-color .2s ease,color .2s ease}.site-header{display:flex;justify-content:space-between;padding:1.25rem 2rem;border-bottom:1px solid var(--border);background:var(--surface)}.brand,.avatar{display:grid;place-items:center;background:var(--accent);color:var(--accent-contrast);font-weight:800}.brand{width:2.75rem;height:2.75rem;border-radius:50%;text-decoration:none}.theme-toggle{padding:.65rem 1rem;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:var(--text);cursor:pointer}.page-shell{width:920px;margin:0 auto;padding:4rem 0}.hero{display:grid;grid-template-columns:220px 1fr;gap:3rem;margin-bottom:2rem}.avatar{width:220px;aspect-ratio:1;border-radius:2rem;font-size:3rem}.panel{padding:1.75rem;border:1px solid var(--border);border-radius:1.25rem;background:var(--surface);box-shadow:var(--shadow)}.panel+.panel{margin-top:1.5rem}.chip-list{display:flex;flex-wrap:wrap;gap:.75rem;padding:0;list-style:none}.project-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.project-card{padding:1.25rem;border:1px solid var(--border);border-radius:1rem}.site-footer{text-align:center;color:var(--muted)}@media (max-width:960px){.site-header{padding:1rem}.page-shell{width:auto;margin:0;padding:2.5rem 1rem}.hero{grid-template-columns:1fr;gap:1.5rem}.avatar{width:120px;border-radius:1.25rem;font-size:2rem}h1{font-size:2.1rem}.project-grid{grid-template-columns:1fr}}
Connect the theme button

The native button already supports mouse plus keyboard activation. One event listener can respond to both input methods through the button's click behavior.

  • Switch back to the empty script.js tab.
  • Add the theme-switching behavior by pasting this code:
const themeToggle = document.querySelector("#theme-toggle");

themeToggle.addEventListener("click", () => {
  const nextTheme = document.body.dataset.theme === "light" ? "dark" : "light";
  document.body.dataset.theme = nextTheme;
  themeToggle.ariaPressed = nextTheme === "dark" ? "true" : "false";
});

What does this code do?

  • document.querySelector finds the theme button through its #theme-toggle ID.
  • addEventListener runs the callback whenever the button receives a click event.
  • nextTheme stores the theme that should replace the current value.
  • dataset writes that value to the body's data-theme attribute.
  • ariaPressed keeps the button's accessibility state synchronized with the visible theme.
  • Save script.js.
  • Return to the page in Microsoft Edge.
  • Refresh the page.

Before you try the button, which color theme do you expect after the first click?

  • Click the Toggle color theme button.

You'll see the entire page switch to the dark palette.

  • Click the Toggle color theme button again.

You'll see the light palette return. The button now controls the page state.

Button still not switching themes?

Confirm script.js is saved.

Check that #theme-toggle still matches the button ID in index.html.

Need another pair of eyes? Help me debug the theme button.

✔️ Awesome, I've got everything!

Your JavaScript now switches the visual theme plus the button's pressed state.

ⓧ I'd like to double check the full code

const themeToggle = document.querySelector("#theme-toggle");

themeToggle.addEventListener("click", () => {
  const nextTheme = document.body.dataset.theme === "light" ? "dark" : "light";
  document.body.dataset.theme = nextTheme;
  themeToggle.ariaPressed = nextTheme === "dark" ? "true" : "false";
});
Test mouse and keyboard activation

An accessible toggle state gives assistive technology the same on or off information that sighted visitors get from the colors.

  • Refresh the page in Microsoft Edge.
  • Right-click the Toggle color theme button.
  • Select Inspect.
  • Confirm the selected button shows aria-pressed="false".

Before you activate the button, do you expect its visual state plus its accessibility state to change together?

  • Click the Toggle color theme button.

You'll see the page switch to dark colors. DevTools now shows aria-pressed="true" on the button.

  • Press Space while the button remains focused.

You'll see the light palette return. DevTools now shows aria-pressed="false".

Space key not changing the theme?

Click the theme button once to place keyboard focus on it.

Press Space again while the button has focus.

Still stuck? Help me test the button with my keyboard.

You have the full interaction working: mouse clicks plus keyboard activation now drive the same theme state.

Secret mission

Filter the Project Cards

Your project cards currently stay visible as one collection. Add accessible filter buttons that let visitors show projects tagged with HTML, CSS, or JavaScript.

Clean Up Your Resources

Clean Up Your Resources

Everything you built stays inside the local profile-page folder, so keeping it creates no ongoing costs. Choose the option that fits what you want to do next.

Resources you used:

  • The local profile-page folder containing index.html, styles.css, and script.js.

Keep everything running

No action needed. Choose this if you want to keep editing or demonstrating your profile page.

  • Leave the profile-page folder in its current location.
  • Double-click index.html whenever you want to demonstrate the profile page in Microsoft Edge.

Pause - I'll come back to this later

Pausing means closing the open apps while keeping your local files. The folder does not run a background process.

  • Close any open Visual Studio Code windows.
  • Close any open Microsoft Edge windows.
  • Keep the profile-page folder in its current location.

Delete - I don't want to use this again

Deleting the folder removes all three page files from their saved location. Choose this option when you no longer need the project.

  • Find the profile-page folder in File Explorer.
  • Right-click the profile-page folder.
  • Select Delete from the menu.
  • Confirm the deletion if Windows asks.
  • Return to the folder's previous location in File Explorer.

You should no longer see the profile-page folder in that location. This confirms that the local project has been removed.

Nice Work!

Nice Work!

You did it! Your interactive profile page now looks polished across desktop and mobile screens.

You've learned how to:

  • Build a clear profile with semantic HTML that keeps every section readable in a logical order.
  • Create a polished interface with CSS custom properties plus CSS Grid. Repair its narrow-screen overflow with a responsive media query.
  • Connect a native theme button to JavaScript click events. Keep the page theme synchronized with its accessible ariaPressed state.
  • Secret Mission: Coordinate multiple project filters to show matching cards. Keep each filter's active style synchronized with its pressed state.

Ready to quiz yourself?