A Semantic First Webpage
Introduction
This lesson belongs to course 1 of the Meta Front-End Developer pathway and teaches the foundation everything else rests on: HTML that describes what content is, and CSS that describes how it looks. React components, which come later, ultimately produce HTML. If your HTML is wrong, no framework will rescue it, and the people who suffer most are those using screen readers, keyboards, or search engines.
In this lesson you build the first page of the Harbor Workshops project: an accessible tutor profile for a community workshop. You will not copy a course assignment; the brief below is original to OnLevelUp.
After this lesson you will be able to write a well-structured HTML document, choose elements for their meaning, write useful alternative text, and apply the basics of the CSS cascade and box model.
Learning objectives
- Write a complete HTML5 document with correct boilerplate.
- Choose headings, lists, links, and landmark elements by meaning, not appearance.
- Write alternative text appropriate to an image's purpose.
- Explain the CSS cascade: origin, specificity, and source order.
- Predict the size of a box using the box model and
box-sizing. - Build and review a small accessible profile page.
1. The HTML document
HTML (HyperText Markup Language) wraps content in elements. An element has an opening tag, content, and a closing tag. Elements can have attributes, name/value pairs inside the opening tag.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Maya Chen, Pottery Tutor | Harbor Workshops</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<h1>Maya Chen</h1>
<p>Pottery tutor at Harbor Workshops.</p>
</body>
</html>
What each piece does:
| Piece | Purpose |
|---|---|
<!doctype html> |
Puts the browser in standards mode |
lang="en" |
Declares the page language; screen readers choose pronunciation from it, and translation tools rely on it |
charset="utf-8" |
Makes accented characters and symbols display correctly |
| viewport meta | Lets mobile browsers use the real device width (more in lesson 1.3) |
<title> |
Appears in the tab, in search results, and is the first thing a screen reader announces |
The <head> contains information about the page. The <body> contains what visitors see. Browsers are forgiving about mistakes and quietly repair them, so you cannot judge correctness by appearance. Use the W3C Nu HTML Checker to validate.
2. Semantic elements: meaning over appearance
A semantic element communicates its role. <button> says "this is clickable and does something". A <div> says nothing. Browsers, search engines, and assistive technologies use meaning to build navigation tools: a screen-reader user can jump from heading to heading, list to list, or landmark to landmark.
Headings
Headings <h1> to <h6> build the outline of the page. Rules of thumb:
- One
<h1>per page, describing the page's main topic. - Do not skip levels (
<h2>then<h4>) because you like the font size. Use CSS for size. - Headings are not for making text bold.
Text, lists, and links
<p>Maya has taught ceramics for <strong>twelve years</strong>.</p>
<h2>Classes this term</h2>
<ul>
<li>Wheel basics (Mondays)</li>
<li>Glazing workshop (Saturdays)</li>
</ul>
<h2>Preparation steps</h2>
<ol>
<li>Wear clothes you can get dirty.</li>
<li>Arrive ten minutes early.</li>
<li>Bring a towel.</li>
</ol>
<p>Read the <a href="/workshops/pottery">pottery workshop details</a>.</p>
<ul>is an unordered list;<ol>is ordered, so use it when sequence matters.<strong>marks strong importance;<em>marks emphasis. Screen readers may change tone.<b>and<i>are for stylistic offsets without added importance.- Link text must make sense out of context. Screen-reader users often list all links on a page. "Click here" repeated five times is useless; "pottery workshop details" is meaningful.
- Links navigate; buttons act. If clicking changes the page's URL, use
<a>. If it submits or toggles something, use<button>.
Landmarks
Landmark elements divide the page into regions that assistive technology can jump between.
| Element | Role | Use |
|---|---|---|
<header> |
Introductory content | Site logo and title (or the intro of a section) |
<nav> |
Navigation links | Main menu; label it with aria-label if you have several |
<main> |
Primary content | Exactly one per page |
<section> |
A thematic group, with a heading | Classes, reviews |
<article> |
Self-contained content | A workshop card, a blog post |
<aside> |
Related but secondary | Tips, side notes |
<footer> |
Closing content | Contact info, legal links |
Overusing <div> is the classic beginner pattern called "div soup". A <div> is fine when no semantic element fits, typically for grouping elements for layout.
Worked example 1: converting div soup
Problem. A previous developer wrote this:
<div class="top">
<div class="big">Harbor Workshops</div>
<div class="links">
<span onclick="go('home')">Home</span>
<span onclick="go('classes')">Classes</span>
</div>
</div>
<div class="main">
<div class="big">Maya Chen</div>
<div>Pottery tutor</div>
</div>
Visually it works. Convert it to semantic HTML.
Reasoning, step by step.
- The top block is introductory content with the site name and navigation: use
<header>, with the site name in a paragraph or heading and a<nav>for links. - The "links" are
<span>s with click handlers. A span is not focusable by keyboard and is not announced as a link. Each destination is a new page, so use<a href>. - The main block is the primary content:
<main>. "Maya Chen" is the page's topic:<h1>. - "Pottery tutor" is a paragraph:
<p>.
Solution.
<header>
<p class="site-name">Harbor Workshops</p>
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/classes">Classes</a></li>
</ul>
</nav>
</header>
<main>
<h1>Maya Chen</h1>
<p>Pottery tutor</p>
</main>
Why other options are worse. Adding role="link" and tabindex="0" to the spans would recreate part of an anchor's behavior by hand, still without right-click, middle-click, or status-bar previews. The first rule of ARIA is: if a native element does the job, use it.
3. Images and alternative text
<img src="/img/maya-wheel.jpg" alt="Maya guiding a student's hands on a spinning clay pot" width="640" height="427">
altis required. It is read by screen readers and displayed when the image fails to load.- Write alt text according to the image's purpose:
| Image role | Example | Alt text |
|---|---|---|
| Informative | Photo of the studio | Describe what matters: "Studio with six pottery wheels and shelves of glazed bowls" |
| Functional | Icon inside a link or button | Describe the action: "Search" not "magnifying glass" |
| Decorative | Ornamental divider | Empty alt, alt="", so assistive technology skips it |
| Complex | Chart | Short alt plus a full description nearby in text |
- Do not start with "image of" or "picture of"; screen readers already announce an image.
- Always give
widthandheightattributes (or an equivalent CSS aspect ratio). The browser reserves space before the image loads, preventing the page from jumping, which is called layout shift. - Use
loading="lazy"for images far below the fold.
4. CSS basics
CSS (Cascading Style Sheets) is a set of rules. Each rule has a selector and declarations.
h1 {
color: #1f3a5f;
font-size: 2rem;
}
You can attach CSS in three ways: an external file via <link> (preferred, cacheable), a <style> element, or a style attribute (hard to override, avoid).
Selectors
| Selector | Example | Matches |
|---|---|---|
| Type | p |
All paragraphs |
| Class | .card |
Elements with class="card" |
| ID | #intro |
The element with id="intro" (IDs must be unique) |
| Descendant | nav a |
Any link inside a nav |
| Child | ul > li |
Direct children only |
| Attribute | a[target="_blank"] |
Links that open a new tab |
| Pseudo-class | a:hover, button:focus-visible |
A state |
Prefer classes for styling; they are reusable and have moderate specificity.
The cascade
When several rules target the same property on the same element, the browser chooses a winner in this order:
- Importance and origin:
!importantdeclarations win; author styles beat the browser's default styles. - Specificity: a more specific selector wins. Roughly, inline style > IDs > classes/attributes/pseudo-classes > type selectors.
- Source order: if everything else ties, the later rule wins.
p { color: black; }
.note { color: darkgreen; }
p.note { color: navy; }
For <p class="note">, all three match. p has specificity (0,0,1), .note is (0,1,0), p.note is (0,1,1). The result is navy. Reordering the rules would not change it, because specificity is compared before order.
Inheritance: some properties (color, font-family, line-height) pass from parent to children; others (border, margin, padding) do not. This is why setting font-family on body styles the whole page.
Units
pxis an absolute pixel.remis relative to the root font size (usually 16px). Preferremfor text and spacing; users who increase their default font size are respected.emis relative to the element's own font size.%is relative to the parent.
The box model
Every element is a rectangular box: content, then padding, then border, then margin (outside space).
By default (box-sizing: content-box), width sets only the content width. Consider:
.card {
width: 300px;
padding: 20px;
border: 5px solid #ccc;
}
Total horizontal space: 300 + 2×20 + 2×5 = 350px. With box-sizing: border-box, width includes padding and border, so the card is exactly 300px wide and the content shrinks to 250px. Most projects start with:
*, *::before, *::after {
box-sizing: border-box;
}
Vertical margins between adjacent block elements can collapse: a 20px bottom margin above a 30px top margin results in a 30px gap, not 50px.
Block and inline
Block elements (div, p, h1, section) start on a new line and fill the width. Inline elements (a, strong, span, img) flow within a line and ignore width and height (for non-replaced ones). display: inline-block combines behaviors, and display: none removes an element from layout and from the accessibility tree.
5. Building the tutor profile page
Brief (original). Harbor needs a profile page for tutor Maya Chen with: her name, a photo, a short biography, a list of classes, and a "Book a class" link. It must work with the keyboard, have good contrast, and look fine at 360px width.
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Maya Chen, Pottery Tutor | Harbor Workshops</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<a class="skip-link" href="#content">Skip to main content</a>
<header>
<p class="site-name">Harbor Workshops</p>
<nav aria-label="Main">
<ul class="nav-list">
<li><a href="/">Home</a></li>
<li><a href="/tutors" aria-current="page">Tutors</a></li>
</ul>
</nav>
</header>
<main id="content">
<article class="profile">
<h1>Maya Chen</h1>
<img src="/img/maya.jpg" alt="Maya smiling beside a shelf of glazed bowls" width="320" height="320">
<p>Maya has taught ceramics for twelve years and loves helping first-timers shape their first bowl.</p>
<h2>Classes this term</h2>
<ul>
<li>Wheel basics (Mondays, 18:00)</li>
<li>Glazing workshop (Saturdays, 10:00)</li>
</ul>
<a class="button" href="/book?tutor=maya">Book a class</a>
</article>
</main>
<footer><p>Harbor Workshops, Quay Street 12</p></footer>
</body>
</html>
styles.css:
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.5;
color: #1a1a1a;
}
.skip-link {
position: absolute;
left: -999px;
}
.skip-link:focus { left: 1rem; top: 1rem; background: #fff; padding: .5rem; }
header, main, footer { padding: 1rem; }
.nav-list { display: flex; gap: 1rem; list-style: none; padding: 0; }
.profile { max-width: 40rem; margin: 0 auto; }
.profile img { max-width: 100%; height: auto; border-radius: .5rem; }
.button {
display: inline-block;
padding: .75rem 1.25rem;
background: #1f3a5f;
color: #fff;
text-decoration: none;
border-radius: .25rem;
}
.button:hover, .button:focus-visible { background: #14263f; }
a:focus-visible { outline: 3px solid #e08a00; outline-offset: 2px; }
Notes on decisions:
- The skip link lets keyboard users bypass the navigation. It is hidden off-screen but becomes visible on focus. Do not use
display: nonefor it, because it would then never receive focus. max-width: 100%; height: autokeeps the image inside narrow screens.- The visible focus outline is retained and made clearer. Removing it with
outline: noneand no replacement is an accessibility failure. #1f3a5fon white has a contrast ratio well above 4.5:1, the WCAG AA minimum for normal text. White text on the button gives a similar ratio.aria-current="page"tells assistive technology which link is the current page.
Predicted behavior. At 360px width the nav items sit in a row, the image shrinks to the container, and the button remains easy to tap. Pressing Tab first reaches the skip link, then "Home", "Tutors", and "Book a class", each with a visible outline.
6. Common pitfalls and exam tips
- Using headings for styling or skipping levels.
- Using
<br>for spacing between paragraphs (use margin). - Placing block elements inside
<p>, which the parser auto-closes. - Missing
altor writing "image of…". - Removing focus outlines.
- Relying on color alone to convey meaning (for example, red text for errors without an icon or text).
- Setting fixed pixel heights that clip text when users enlarge fonts.
- Selectors that are too specific (
#page .content ul li a) and hard to override. Prefer a single class.
Exam tip. When asked which element to use, choose the one whose meaning matches the content. "Which is best for a set of related links?" is <nav> containing a list. "Which text is read by screen readers when an image fails?" is alt.
Exam tip. To compute total box width, add content, padding, border (both sides), and margin where relevant, and note the box-sizing value first.
Mini case study: the clickable span
Situation. A usability test shows that a keyboard-only participant cannot reach the "Book a class" control on a draft profile. The control is <span class="button" onclick="book()">Book a class</span>. Also, the photo has alt="image" and the page has two <h1> elements.
Task. List the problems and propose fixes.
Solution.
- Span with click handler. A span is not in the tab order and exposes no role. If booking navigates to a new page, use
<a href="/book?tutor=maya">; if it triggers an action in the page, use<button type="button">. Both are focusable and activated by keyboard natively. alt="image". It conveys nothing. Replace it with a purposeful description such as "Maya smiling beside a shelf of glazed bowls".- Two
<h1>s. Keep one<h1>for the page topic (the tutor name); demote the other to<h2>, adjusting size with CSS.
Check. Tab reaches the control; a screen reader announces "link, Book a class" (or "button"); the outline lists one <h1> followed by <h2>s.
Key terms
- Element / attribute: building block of HTML / extra information on an element.
- Semantic HTML: elements chosen for meaning.
- Landmark: region (header, nav, main, footer) used for navigation by assistive tech.
- Alt text: text alternative for an image.
- Selector / declaration: what a CSS rule targets / the property-value pair.
- Cascade / specificity: how conflicting rules are resolved.
- Box model: content, padding, border, margin.
- Focus: the element currently receiving keyboard input.
Summary checklist
- My document has doctype,
lang, charset, viewport, and a descriptive title. - I used one
<h1>, no skipped heading levels, and landmark elements. - Links have meaningful text and buttons are real buttons.
- Every image has a purposeful
alt(empty if decorative) and dimensions. - I can predict which of several CSS rules wins and why.
- I can calculate box width with and without
border-box. - Keyboard focus is visible and the page is usable by Tab alone.
Official sources
- Meta Front-End Developer Professional Certificate (opens in a new tab)
- Course 1: Introduction to Front-End Development (opens in a new tab)
- MDN: HTML elements reference (opens in a new tab)
- MDN: Cascade, specificity, and inheritance (opens in a new tab)
- W3C WAI: Images tutorial (opens in a new tab)
- WCAG 2.2 Quick Reference (opens in a new tab)
