On this page
This capstone combines the Web Foundations path into one page. You will write a complete document, choose semantic landmarks, style reusable classes, control sizing with the box model, and enhance a narrow layout with Flexbox.
The result is deliberately a landing page rather than another profile card. It has site-level navigation, several content sections, two project summaries, and a closing profile-links panel. JavaScript is not required.
Skills practiced in context
This build draws directly from the path:
- HTML document structure supplies the doctype, language, head, title, and body.
- Semantic HTML gives the page a header, navigation, main region, sections, articles, and footer.
- Selectors and the cascade explain why reusable class rules apply.
- The box model keeps padding and borders inside flexible widths.
- Responsive Flexbox arranges the hero, project cards, and contact panel when space allows.
You do not need to remember every declaration. Keep those lessons open and use browser tools when a result differs from your prediction.
Before you start
Choose a name or site label, one honest sentence about what you are learning, three current skills, and two small projects you can describe. They can be practice exercises; do not invent clients, employment, or results.
The final example includes two placeholder profile destinations. Replace them with real public URLs before publishing. If you do not have a public profile or personal site, remove that link instead of leaving a fake destination.
Plan the page content
Write the content before tuning colors. A useful outline is:
- A header identifies the site and links to About, Projects, and Profile links.
- The introduction states what you build or study.
- An About section adds context without becoming a full biography.
- A skill list records what you are currently practicing.
- Two project articles give specific examples.
- A profile-links section explains how to find public work.
- The footer closes the document.
This order also makes sense if CSS fails: readers receive the introduction before supporting details.
Create the file structure
Create a folder with two files:
personal-landing-page/
├── index.html
└── styles.css
Keep the filenames lowercase. The relative stylesheet link in index.html must match styles.css exactly.
Write the HTML document
Start with the document shell and connect the stylesheet:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="A personal landing page for a developer learning web foundations."
/>
<title>Alex Morgan — Frontend learner</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- Page landmarks will go here. -->
</body>
</html>
Replace the name and description with accurate text. The metadata describes the document, while the visible <h1> added next will introduce the page itself.
Add semantic page landmarks
Use a page-level <header> and <footer> around one <main>. Inside main, each major topic is a headed <section>. The project summaries are <article> elements because each is a self-contained item.
The navigation links use real fragment destinations: #about, #work, and #contact. Every fragment value has one matching id in the document.
The skill list sits in an <aside> beside the introduction because it supports that primary message. It still has a heading, so readers know what the list represents.
Do not choose a <section> just because CSS will draw a box. Choose it for a thematic part of the page; use classes for the visual design.
Create reusable CSS custom properties
Custom properties give repeated design values readable names:
:root {
--color-ink: #17203a;
--color-muted: #5f6982;
--color-accent: #3946d6;
--color-focus: #008ba0;
--color-surface: #ffffff;
--color-page: #f4f5fa;
--color-border: #d5dae8;
--radius: 1rem;
--container: 70rem;
}
var(--color-accent) can now be used by links, eyebrows, and the primary action. Changing the custom property changes those uses together. The name describes a role instead of a particular shade, so later adjustments remain understandable.
Apply the box model
Use inherited border-box sizing across elements and generated pseudo-elements:
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
The page containers combine the available width with a readable maximum:
.site-header,
main,
footer {
width: min(var(--container), calc(100% - 2rem));
margin-inline: auto;
}
At narrow widths, calc(100% - 2rem) leaves one rem on each side. At wide widths, the content stops growing at 70rem. Card padding and borders stay inside their flexible widths because of border-box sizing.
Build the mobile layout first
The base layout uses normal flow and Flexbox columns. The hero, project list, and contact panel all begin stacked:
.hero,
.project-list,
.contact-panel {
display: flex;
flex-direction: column;
}
Content determines height. A responsive clamp() controls generous but bounded vertical spacing, and the heading uses a maximum line length instead of a fixed box.
Test this version before adding a media query. At 390px and 430px, navigation should wrap, links should remain visible, and cards should use the available width without clipping.
Add the Flexbox enhancement
At 48rem, the content has room for selected rows:
@media (width >= 48rem) {
.hero {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.learning-card {
flex: 0 1 22rem;
}
.project-list {
flex-flow: row wrap;
}
.project-card {
flex: 1 1 18rem;
}
.contact-panel {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
The project cards use flex: 1 1 18rem, so they share a row when possible and wrap before becoming too narrow. The media query does not claim to detect a tablet. It responds to available width.
Style links and focus states
All navigation and project controls are links because they have destinations. The prominent hero link is still an anchor even though CSS makes it look like a button.
a:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
border-radius: 0.2rem;
}
The outline changes shape as well as color and remains outside the link. Do not remove it. Hover styling can be a useful extra, but every action must remain understandable without hover.
Complete final HTML
Replace your temporary body with the complete content below. Update the name, text, and profile destinations before publishing.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="A personal landing page for a developer learning web foundations."
/>
<title>Alex Morgan — Frontend learner</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="site-header">
<a class="site-name" href="#main-content">Alex Morgan</a>
<nav aria-label="Primary navigation">
<a href="#about">About</a>
<a href="#work">Projects</a>
<a href="#contact">Links</a>
</nav>
</header>
<main id="main-content">
<section class="hero" aria-labelledby="page-title">
<div>
<p class="eyebrow">Frontend learner</p>
<h1 id="page-title">I build clear, useful pages for the web.</h1>
<p class="hero-copy">
I am learning HTML and CSS by turning small ideas into responsive, accessible pages.
</p>
<a class="button-link" href="#work">View practice projects</a>
</div>
<aside class="learning-card" aria-labelledby="learning-title">
<h2 id="learning-title">Currently practicing</h2>
<ul>
<li>Semantic HTML</li>
<li>Responsive CSS</li>
<li>Keyboard testing</li>
</ul>
</aside>
</section>
<section id="about" class="page-section" aria-labelledby="about-title">
<p class="eyebrow">About</p>
<h2 id="about-title">Learning by building small, complete pages</h2>
<p>
My goal is to understand the browser's foundations before adding frameworks. I keep notes,
test with a keyboard, and improve one detail at a time.
</p>
</section>
<section id="work" class="page-section" aria-labelledby="work-title">
<p class="eyebrow">Selected practice</p>
<h2 id="work-title">Projects</h2>
<div class="project-list">
<article class="project-card" id="profile-card-details">
<h3>Profile card</h3>
<p>A compact card with semantic content, flexible sizing, and clear focus styles.</p>
<a href="#profile-card-details">Read the profile-card summary</a>
</article>
<article class="project-card" id="notes-page-details">
<h3>Learning notes page</h3>
<p>A structured page that uses landmarks and reusable CSS classes.</p>
<a href="#notes-page-details">Read the learning-notes summary</a>
</article>
</div>
</section>
<section id="contact" class="page-section contact-panel" aria-labelledby="contact-title">
<div>
<p class="eyebrow">Profile links</p>
<h2 id="contact-title">Find my work</h2>
<p>Replace the example destinations below with your real public profiles.</p>
</div>
<div class="profile-links">
<a href="https://github.com/replace-with-your-name">Replace with your GitHub profile</a>
<a href="https://example.com/replace-with-your-site">Replace with your website</a>
</div>
</section>
</main>
<footer>
<p>Built with semantic HTML and responsive CSS.</p>
</footer>
</body>
</html>
The two project-summary links are local fragment examples, so their targets exist in this document. Replace them with real project routes when you publish separate project pages.
Complete final CSS
:root {
--color-ink: #17203a;
--color-muted: #5f6982;
--color-accent: #3946d6;
--color-focus: #008ba0;
--color-surface: #ffffff;
--color-page: #f4f5fa;
--color-border: #d5dae8;
--radius: 1rem;
--container: 70rem;
}
html {
box-sizing: border-box;
scroll-behavior: smooth;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
background: var(--color-page);
color: var(--color-ink);
}
.site-header,
main,
footer {
width: min(var(--container), calc(100% - 2rem));
margin-inline: auto;
}
.site-header {
min-height: 4.75rem;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.75rem 1.5rem;
}
.site-name {
color: var(--color-ink);
font-weight: 850;
text-decoration: none;
}
nav,
.profile-links {
display: flex;
flex-wrap: wrap;
gap: 0.75rem 1.25rem;
}
a {
color: var(--color-accent);
font-weight: 750;
text-underline-offset: 0.2em;
}
a:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
border-radius: 0.2rem;
}
.hero {
display: flex;
flex-direction: column;
gap: 2rem;
padding-block: clamp(4rem, 12vw, 8rem);
}
.eyebrow {
margin-bottom: 0.65rem;
color: var(--color-accent);
font-size: 0.8rem;
font-weight: 850;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1,
h2,
h3 {
line-height: 1.1;
}
h1 {
max-width: 15ch;
margin-block: 0 1.25rem;
font-size: clamp(2.5rem, 9vw, 5.5rem);
}
.hero-copy,
.page-section > p {
max-width: 62ch;
color: var(--color-muted);
line-height: 1.75;
}
.button-link {
min-height: 2.75rem;
display: inline-flex;
align-items: center;
margin-top: 1rem;
padding: 0.75rem 1rem;
border-radius: 0.7rem;
background: var(--color-accent);
color: white;
text-decoration: none;
}
.learning-card,
.project-card,
.contact-panel {
padding: clamp(1.25rem, 4vw, 2rem);
border: 1px solid var(--color-border);
border-radius: var(--radius);
background: var(--color-surface);
}
.learning-card h2,
.project-card h3 {
margin-top: 0;
}
.learning-card li + li {
margin-top: 0.65rem;
}
.page-section {
padding-block: clamp(3rem, 8vw, 5rem);
border-top: 1px solid var(--color-border);
}
.project-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1.5rem;
}
.project-card p {
color: var(--color-muted);
line-height: 1.65;
}
.contact-panel {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-bottom: 4rem;
}
.profile-links {
align-items: flex-start;
}
footer {
padding-block: 2rem;
border-top: 1px solid var(--color-border);
color: var(--color-muted);
}
@media (width >= 48rem) {
.hero {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.learning-card {
flex: 0 1 22rem;
}
.project-list {
flex-flow: row wrap;
}
.project-card {
flex: 1 1 18rem;
}
.contact-panel {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
Test keyboard access
Reload the page and use only the keyboard:
- Press Tab through the site name, navigation, primary project link, project links, and profile links.
- Confirm the focus indicator is visible on every link.
- Press Enter on each fragment link and confirm its destination exists.
- Check that focus order follows the visible reading order.
- Confirm that no action requires hover.
The example has no custom JavaScript, modal, or menu, so native link behavior should do the work.
Test at 200% zoom
Set browser zoom to 200%. Text should reflow without being clipped. Navigation and profile links may wrap onto additional lines. That is acceptable; losing content or requiring two-dimensional page scrolling is not.
If a fixed height clips a card, remove it. If a long URL overflows, use meaningful link text instead of displaying the entire address and consider overflow-wrap for user-generated content.
Test responsive widths
Responsive testing checklist
- At 390px, the page has no horizontal scrollbar and the hero forms one column.
- At 430px, navigation wraps without colliding with the site name.
- At 768px, the Flexbox enhancement has enough room for a balanced hero.
- At 1024px and wider, line lengths remain controlled by the container and text limits.
- At every width, project cards wrap before their content becomes cramped.
- At 200% zoom, all text and links remain reachable.
- With reduced motion enabled, smooth scrolling is removed.
- The example profile destinations are replaced or removed before publishing.
Common mistakes
Starting with a fixed desktop canvas. A width such as 1200px can overflow. Start from normal flow and cap a flexible container.
Using visual wrappers instead of semantic content. Keep headings, lists, sections, articles, navigation, and footer meaningful before adding classes.
Adding Flexbox to every element. Use it where a one-dimensional relationship needs control. Normal document flow remains valuable.
Removing link underlines and focus without alternatives. Links need to be recognizable in their context and visibly focused from the keyboard.
Publishing placeholder profiles. Replace or remove every example URL. Do not send readers to a destination that is not yours.
Testing only one phone preset. Resize between 390px, 430px, 768px, and desktop. Content—not a device label—reveals the breakpoint problems.
Practice
Optional improvements
Replace the two project summaries with your own accurate work. Add a “Now learning” sentence to the About section. Try a second color theme by changing only custom properties. If you later add an image, give it useful alternative text when it communicates information, set dimensions, and keep it within its container.
What to learn next
Keep this page as a baseline and improve it as your skills grow. Continue with JavaScript Essentials when the page needs purposeful interaction. When you are ready to organize repeated layout and content into components, Create Your First Astro Website provides the next bridge without discarding the semantic HTML and responsive CSS you wrote here.