Overview
A personal portfolio page is the first project where HTML tag choice actually matters beyond "does it work". A portfolio is read by humans skimming for information, by search engines deciding how to rank it, and by screen readers announcing its structure out loud. Wrapping every block of content in a generic <div> would render identically on screen, but it throws away the meaning that semantic elements like <header>, <main>, <section>, <article>, and <footer> give away for free.
In this tutorial you will build a single-page portfolio for a fictional developer, Asha Verma, structured entirely with semantic HTML5 landmarks: a page header with navigation, an about section, a skills list, a set of project write-ups, and a real contact form, all wired together with fragment links so the navigation actually jumps to each section.
- A semantic `<header>` containing a name, tagline, and a `<nav>` menu of jump links.
- An "About" `<section>` introducing the developer with a photo and bio.
- A "Skills" `<section>` presented as a proper `<ul>` list, not a wall of text.
- A "Projects" `<section>` made of individual `<article>` elements, one per project.
- A "Contact" `<section>` with a labeled `<form>` for name, email, and message.
- A semantic `<footer>` closing out the page with a copyright line.
Prerequisites
- Basic HTML document structure — `<!doctype html>`, `<html>`, `<head>`, and `<body>`.
- How to write and nest HTML elements, and the difference between block-level and inline elements.
- Attributes in general — you'll use `href`, `id`, `src`, `alt`, and several form-specific attributes.
- A text editor and a browser to open the finished `.html` file in.
Project Structure
The whole page lives in one file, `portfolio.html`. Inside `<body>`, three landmark regions sit one after another: `<header>` (name, tagline, navigation), `<main>` (the actual content, split into four `<section>` elements), and `<footer>` (closing copyright line). Every section that the nav menu links to gets a matching `id` attribute — `#about`, `#skills`, `#projects`, `#contact` — so each `<a href="#about">` in the nav jumps straight to that part of the page with no JavaScript involved.
Choosing `<section>` over `<div>` for each of these blocks is deliberate: a `<section>` groups content that shares one theme and, ideally, starts with its own heading — exactly what "About", "Skills", "Projects", and "Contact" each are. A `<div>` carries no such meaning; it is a fallback for when nothing more specific fits, which is not the case here.
Step 1: Scaffold the Document and Header
Start with the standard HTML5 boilerplate, then add the page's `<header>`. Because this content — the name, tagline, and navigation menu — applies to the entire page rather than to one specific topic, `<header>` is the correct landmark, and `<nav>` is correct for the link list inside it since it is genuinely a navigation menu, not just a random list of links.
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <!-- Ensures special characters (accents, symbols) render correctly --> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Makes the layout responsive on mobile --> <title>Asha Verma - Frontend Developer Portfolio</title> <!-- Shown in the browser tab and search results --> <meta name="description" content="Portfolio of Asha Verma, a frontend developer specializing in accessible, semantic web interfaces."></head><body>
<!-- <header> instead of <div>: this banner content (name, tagline, nav) applies to the whole page, and browsers/assistive tech treat <header> as a distinct landmark --> <header> <h1>Asha Verma</h1> <!-- The single most important heading on the page --> <p>Frontend Developer & Accessibility Advocate</p> <!-- Tagline, plain paragraph is fine here -->
<!-- <nav> marks this list of links as a navigation landmark, letting assistive tech users jump straight to "the menu" instead of reading every link on the page --> <nav> <ul> <!-- <ul> is correct here: it's a genuine list of navigation destinations --> <li><a href="#about">About</a></li> <!-- Targets the id added in Step 2 --> <li><a href="#skills">Skills</a></li> <!-- Targets the id added in Step 3 --> <li><a href="#projects">Projects</a></li> <!-- Targets the id added in Step 4 --> <li><a href="#contact">Contact</a></li> <!-- Targets the id added in Step 5 --> </ul> </nav> </header>
<!-- <main> and <footer> are added in the steps that follow -->
</body></html>The four nav links point at `#about`, `#skills`, `#projects`, and `#contact` — none of those ids exist yet, so clicking them right now would do nothing. They start working the moment the matching section ids are added in the next few steps.
Step 2: Write the About Section
The About section is the first thing that goes inside `<main>`. Add this section directly inside `<main>`, right after `<header>`.
<!-- id="about" is the target of the header's "About" nav link (<a href="#about">) --><section id="about"> <h2>About Me</h2> <img src="https://placehold.co/160x160" alt="Portrait photo of Asha Verma" width="160" height="160"> <!-- alt text is required: screen readers read it aloud, and browsers show it if the image fails to load --> <p> I'm a frontend developer with three years of experience building accessible, standards-compliant web interfaces. I care about writing HTML that means something, not just HTML that happens to look right. </p></section>Step 3: List Your Skills
The Skills section is just a heading and a list — a perfect case for `<ul>`, since a plain list announces itself to screen readers as "list, 6 items" and lets keyboard/assistive-tech users navigate item by item, something a comma-separated paragraph of text can never offer. Add this section directly after the About section, still inside `<main>`.
<!-- id="skills" is the target of the header's "Skills" nav link --><section id="skills"> <h2>Skills</h2> <!-- <ul> over <div>+<br>: a real list gives assistive tech an item count and per-item navigation --> <ul> <li>Semantic HTML5</li> <li>CSS Grid & Flexbox</li> <li>JavaScript (ES6+)</li> <li>Responsive Design</li> <li>Web Accessibility (WCAG)</li> <li>Git & GitHub</li> </ul></section>Step 4: Showcase Projects with Articles
Each project write-up is wrapped in its own `<article>` rather than a `<div>` because the content is independently meaningful — a single project blurb would still make complete sense pulled out on its own, e.g. shared as a link preview or syndicated in an RSS feed. That "would it stand alone?" test is the quickest way to decide between `<article>` and `<section>`.
<!-- id="projects" is the target of the header's "Projects" nav link --><section id="projects"> <h2>Projects</h2>
<!-- Each <article> is a self-contained project write-up: title, summary, and link --> <article> <h3>Weather Dashboard</h3> <p>A responsive dashboard that pulls live weather data for any city.</p> <a href="https://example.com/weather-dashboard">View project →</a> </article>
<article> <h3>Task Tracker</h3> <p>A drag-and-drop task board built with vanilla JavaScript.</p> <a href="https://example.com/task-tracker">View project →</a> </article>
<article> <h3>Recipe Finder</h3> <p>Search thousands of recipes by ingredient using a public API.</p> <a href="https://example.com/recipe-finder">View project →</a> </article></section>Step 5: Build a Contact Form
The Contact section holds a real `<form>` with three fields. Every `<input>` and `<textarea>` here has a matching `<label for="...">` — clicking the label text focuses the field, and screen readers announce the label the moment the field receives focus. That pairing, more than any styling, is what makes a form actually usable rather than merely functional.
<!-- id="contact" is the target of the header's "Contact" nav link --><section id="contact"> <h2>Contact Me</h2> <!-- action="#" is a placeholder; a real deployment would point this at a backend endpoint --> <form action="#" method="post"> <p> <label for="name">Name</label><br> <!-- for/id pairing: this is what ties the label to the input for accessibility --> <input type="text" id="name" name="name" required> </p> <p> <label for="email">Email</label><br> <input type="email" id="email" name="email" required> <!-- type="email" gives free browser-level format checking before the form ever submits --> </p> <p> <label for="message">Message</label><br> <textarea id="message" name="message" rows="5" required></textarea> <!-- rows sets the visible height; textarea is used instead of input since messages are multi-line --> </p> <button type="submit">Send Message</button> <!-- type="submit" is explicit so this button can't be mistaken for a plain or reset button --> </form></section>Step 6: Add the Footer
The last piece is the `<footer>`, added as a sibling of `<header>` and `<main>` rather than nested inside either — like `<header>`, it is page-wide content (a copyright line), so it gets its own landmark instead of living inside the last section by accident.
<!-- <footer> instead of another <section>: this closing copyright line belongs to the whole page, not to any one topic, so it gets its own page-level landmark --><footer> <p>© 2026 Asha Verma. Built with semantic HTML.</p></footer>Complete Code
Here is the full page with every piece assembled in order, plus a small stylesheet so it renders as a real, presentable page rather than unstyled text.
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Asha Verma - Frontend Developer Portfolio</title> <meta name="description" content="Portfolio of Asha Verma, a frontend developer specializing in accessible, semantic web interfaces."> <style> * { box-sizing: border-box; } /* Predictable box sizing across all elements */ body { font-family: Arial, Helvetica, sans-serif; margin: 0; color: #222; line-height: 1.6; background: #fafafa; } header { background: #2b3a55; color: #fff; text-align: center; padding: 40px 20px; } header h1 { margin: 0 0 8px; font-size: 2rem; } header p { margin: 0 0 16px; color: #d7dce6; } nav ul { list-style: none; display: flex; justify-content: center; gap: 20px; padding: 0; margin: 0; flex-wrap: wrap; } nav a { color: #fff; text-decoration: none; font-weight: bold; } nav a:hover { text-decoration: underline; } main { max-width: 720px; margin: 0 auto; padding: 20px; } section { margin-bottom: 40px; } section h2 { border-bottom: 2px solid #2b3a55; padding-bottom: 6px; } #about img { border-radius: 50%; float: left; margin-right: 20px; margin-bottom: 10px; } #skills ul { padding-left: 20px; } article { background: #fff; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin-bottom: 16px; } article h3 { margin-top: 0; } form p { margin-bottom: 16px; } label { display: block; font-weight: bold; margin-bottom: 4px; } input, textarea { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-family: inherit; } button { background: #2b3a55; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 1rem; } button:hover { background: #1d2940; } footer { text-align: center; padding: 20px; background: #2b3a55; color: #d7dce6; } </style></head><body>
<!-- <header> instead of <div>: this banner content applies to the whole page --> <header> <h1>Asha Verma</h1> <p>Frontend Developer & Accessibility Advocate</p> <nav> <ul> <li><a href="#about">About</a></li> <li><a href="#skills">Skills</a></li> <li><a href="#projects">Projects</a></li> <li><a href="#contact">Contact</a></li> </ul> </nav> </header>
<main> <!-- id="about" is the target of the "About" nav link --> <section id="about"> <h2>About Me</h2> <img src="https://placehold.co/160x160" alt="Portrait photo of Asha Verma" width="160" height="160"> <p> I'm a frontend developer with three years of experience building accessible, standards-compliant web interfaces. I care about writing HTML that means something, not just HTML that happens to look right. </p> </section>
<!-- id="skills" is the target of the "Skills" nav link --> <section id="skills"> <h2>Skills</h2> <ul> <!-- Real list: assistive tech announces "list, 6 items" --> <li>Semantic HTML5</li> <li>CSS Grid & Flexbox</li> <li>JavaScript (ES6+)</li> <li>Responsive Design</li> <li>Web Accessibility (WCAG)</li> <li>Git & GitHub</li> </ul> </section>
<!-- id="projects" is the target of the "Projects" nav link --> <section id="projects"> <h2>Projects</h2> <article> <!-- Standalone, independently meaningful content --> <h3>Weather Dashboard</h3> <p>A responsive dashboard that pulls live weather data for any city.</p> <a href="https://example.com/weather-dashboard">View project →</a> </article> <article> <h3>Task Tracker</h3> <p>A drag-and-drop task board built with vanilla JavaScript.</p> <a href="https://example.com/task-tracker">View project →</a> </article> <article> <h3>Recipe Finder</h3> <p>Search thousands of recipes by ingredient using a public API.</p> <a href="https://example.com/recipe-finder">View project →</a> </article> </section>
<!-- id="contact" is the target of the "Contact" nav link --> <section id="contact"> <h2>Contact Me</h2> <form action="#" method="post"> <p> <label for="name">Name</label><br> <input type="text" id="name" name="name" required> </p> <p> <label for="email">Email</label><br> <input type="email" id="email" name="email" required> </p> <p> <label for="message">Message</label><br> <textarea id="message" name="message" rows="5" required></textarea> </p> <button type="submit">Send Message</button> </form> </section> </main>
<!-- Page-wide closing content, so it's a sibling of <header> and <main>, not nested in them --> <footer> <p>© 2026 Asha Verma. Built with semantic HTML.</p> </footer>
</body></html>Sample Run
Open `portfolio.html` in a browser, or use the live preview above, and try the following:
- Click any link in the header nav — About, Skills, Projects, Contact — and the page jumps straight to that section, because each link's `href` matches a section's `id` exactly.
- Resize the browser window narrower — the layout stays centered and readable because `<main>` has a `max-width` instead of a fixed pixel width.
- Try submitting the Contact form with the Email field left as plain text like "not-an-email" — the browser blocks submission on its own because of `type="email"` and `required`, with no JavaScript written.
- View the page source (or inspect it in devtools) and notice how the landmark elements — header, nav, main, section, article, footer — make the structure readable at a glance, compared to an all-`<div>` version of the same page.
Extend This Project
- Add a "Resume" section with a downloadable PDF link using the `download` attribute on an `<a>` tag.
- Turn the skills list into a definition list (`<dl>`/`<dt>`/`<dd>`) that pairs each skill with a one-line description.
- Add `rel="noopener noreferrer"` and `target="_blank"` to the project links so they open in a new tab safely.
- Lay out the project `<article>` cards in a grid using CSS, without changing any of the underlying semantic markup.
- Add a honeypot field to the contact form as a first line of defense against spam bots.
Summary
You built a complete, single-page portfolio using nothing but semantic HTML: a header landmark for navigation, a main region split into themed sections, article elements for standalone project write-ups, a properly labeled form, and a footer landmark to close things out. The habit of asking "what is the most specific element for this content?" instead of reaching for `<div>` by default is the single biggest structural upgrade you can make to any page you build from here on.