Skip to content
← Back to lessons

Final Project: Bring Your Profile Page to Life

Checking...

You built the house in HTML. You decorated it in CSS. Today, you wire up the electricity.

Before the project, one more look back.

At the start of this track, JavaScript was the language that did "behavior," and your first line was console.log('Hello from JavaScript!'). Remember seeing that message appear, and then watching a heading change its own text?

Look at what you know now. You can store and shape data with variables, arrays, and objects, and pack it into JSON. You can make decisions, repeat work, and package steps into functions that remember things through closures. You can find any element on the page, change it, build new ones from data, and remove them. You can listen for clicks, keys, and form submissions, and replace the browser's generic error messages with friendly, accessible ones. You understand why setTimeout(..., 0) runs late, what a promise is, and how await waits without freezing the page. You can load data from a server, handle the times it fails, and remember a visitor's choices between visits.

And you know the why: why === beats ==, why textContent beats innerHTML, why a real <button> beats a clickable <div>, and why the server is always the security guard.

There were surely moments where it didn't work. A null where an element should have been. A Promise {...} where data should have been. A this that went missing. You read the clue, found the cause, and fixed it. That's the actual skill.

I'm genuinely proud of you. Let's finish the house.

The project ​

In Final Project: Your Profile Page, you built a personal profile page, and in Final Project: Style Your Profile Page, you styled it. Now you'll make it interactive.

This workspace starts with a finished version of that page. The HTML and CSS are done, so you can focus entirely on JavaScript. The script.js tab contains a few preview-only helpers (explained in its comments) and a labeled spot for each requirement. You're welcome to adjust the HTML and CSS too, but keep the ids the requirements mention, because the checker uses them.

Each requirement says what to build, why it matters, and which lesson to review.

Requirements ​

1. A theme toggle that remembers ​

Make the #theme-toggle button switch the page between light and dark:

  • On load, set data-theme on the <html> element to the saved choice, or, for a first-time visitor, to 'dark' or 'light' based on prefers-color-scheme.
  • On click, flip data-theme between 'light' and 'dark', and save the new choice with saveSetting.
  • Keep the button's aria-pressed in sync: 'true' when dark mode is on.

Why: A preference that resets on every visit isn't much of a preference. The CSS already has a dark theme waiting for data-theme="dark", so JavaScript only decides when. Review: Saving Data with localStorage and Where CSS Meets JavaScript.

2. Interests built from data ​

Create an array of at least three interests, and build an <li> for each one inside #interests-list, using createElement and textContent.

Why: Content that lives in data can be sorted, filtered, or loaded from a server later without touching the HTML. Review: Creating and Removing Elements and Arrays.

3. Projects loaded with fetch ​

Write an async function that loads your projects from PROJECTS_URL and shows each one as an <article> (with an <h3> title and a <p> description) inside #project-list.

  • Show a loading message in #projects-status while it works.
  • Check response.ok, and throw an error if it's false.
  • If anything fails, show a friendly message in #projects-status instead.

Why: This is the pattern behind almost every dynamic page on the web: ask, wait, check, show, and plan for failure. Review: Loading Data with fetch and Promises and async/await.

4. A live character counter ​

As someone types in #contact-message, update #message-count to say how many characters are left (the textarea's maxLength is 200).

Why: Helpful feedback while typing, not after, is the difference between a form that guides people and one that scolds them. Review: Forms and Custom Validation.

5. A contact form with friendly validation ​

Listen for the form's submit event and prevent the default. Then check the name and email fields yourself:

  • For each field with a problem, put a clear message in its error element (#name-error, #email-error), and set aria-invalid="true" on the field.
  • Clear the message and aria-invalid for fields that are fine.
  • If anything is wrong, move focus to the first field with a problem.
  • If everything is right, thank the visitor by name in #form-status and reset the form.

Why: Specific, visible, accessible messages help everyone finish the form, including people using screen readers and keyboards. Review: Forms and Custom Validation.

6. Clean, safe, modern code ​

  • const and let only, never var.
  • === and !== only, never == or !=.
  • Put text on the page with textContent, never innerHTML.
  • No uncaught errors in the console.
  • Keep all of your code in script.js. The checker reads that file, so code in a <script> added to index.html won't count.

Why: These are the habits that keep code readable, safe from XSS, and free of the quiet type-juggling bugs. Review: Best Practices and Common Mistakes.

Stretch goals (optional, for the ambitious) ​

  • Show the number of projects in #projects-status once they load ("3 projects").
  • Give the form a live email check that clears its error as soon as the address becomes valid.
  • Add a "Back to top" button that appears after scrolling, using a scroll listener and the hidden property.
  • Add a small filter: a text box that hides interests that don't match what's typed, using filter and includes.
  • Move your code into modules on your own computer, with a separate file for storage helpers, and serve it with a local server.

Using this workspace ​

The tabs on the left switch between these instructions, index.html, style.css, and script.js. The page runs a moment after you stop typing (or when you press Run). About a second after it loads, a small checker at the bottom of index.html tests each requirement and prints a final-project: line in the Console tab for every one that passes, and the Checks tab turns green as they do. Leave the checker at the bottom of index.html where it is. Drag the line between the two sides to give either one more room; on a phone, the Code and Result buttons in the toolbar switch between them. Reset puts the starter code back; press it twice, so a stray click can't wipe your work.

The checker can only see whether each piece works. It can't tell whether your error messages are kind, whether your names are clear, or whether your functions are small. That judgment is yours, which is what the self-check is for.

Self-check ​

This is a building project, not a test, so there's no score. Answer each question honestly, yes or no, about your own code. Every "no" is just a pointer to your next improvement.

  1. If I read my code top to bottom, could I explain what every function does from its name alone?
  2. Does every requirement still work when I use only the keyboard: Tab to the toggle, Space to press it, Enter to submit the form?
  3. When a field has an error, does the message say what's wrong and how to fix it?
  4. If the projects fail to load, does the page still make sense, with a friendly message instead of a blank space?
  5. Did I put every piece of text on the page with textContent, including text that came from data?
  6. Did I use const everywhere a value never changes, and let only where it does?
  7. Are there any empty catch blocks, or errors I'm quietly ignoring?
  8. If I came back to this code in six months, could I change one feature without being afraid of breaking another?
  9. When I use my page, does it feel alive, and like mine?

If you answered yes to all nine, you've built something you can proudly show a future employer, a client, or your family. If a few came back "no," that's completely normal. Go back and improve them. The second pass is where good developers become great ones.

Where you go from here ​

Your page is structured, styled, and now alive. It remembers people, loads fresh content, and guides visitors through a conversation. That's the complete foundation of front-end web development: HTML, CSS, and JavaScript, working together, each doing its own job.

From here, the book branches out. TypeScript adds types that catch mistakes before your code even runs. Frameworks like React and Vue help you build much bigger interfaces without drowning in querySelector calls. And Node.js takes the JavaScript you already know to the server, where you'll finally build the security guard. Whichever way you go, you're going with a solid foundation. Well done.