Skip to content

Web Foundations: HTTP, Semantic HTML, and Responsive CSS

Step 2 of 45

Semantic HTML and CSS: Build an Accessible First Webpage

Introduction

Semantic HTML and CSS let you build an accessible webpage with a clear structure and predictable styles. This lesson belongs to course 1 of the Meta Front-End Developer pathway: HTML describes what content is, and CSS 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

  1. Write a complete HTML5 document with correct boilerplate.
  2. Choose headings, lists, links, and landmark elements by meaning, not appearance.
  3. Write alternative text appropriate to an image's purpose.
  4. Explain the CSS cascade and identify when specificity and source order decide among ordinary author rules.
  5. Predict the size of a box using the box model and box-sizing.
  6. Build and review a small accessible profile page.

1. The HTML document

HTML (HyperText Markup Language) wraps content in elements. Most elements have an opening tag, content, and a closing tag. Void elements such as <img>, <input>, and <meta> have no content or closing tag. Elements can have attributes, name/value pairs inside the opening tag.

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>
    <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> Names the document in the tab and helps users identify it; search engines may rewrite the displayed search title

For the next practical step, study browser requests and URL resolution.

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:

  • Prefer one clear <h1> describing the page's main topic. This is a useful authoring convention, not a claim that every document with two <h1> elements is invalid.
  • 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

HTML
<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 One main element that is not hidden
<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

Not every semantic element automatically creates a landmark. A <section> needs an accessible name, such as aria-labelledby referencing its heading, to expose a region landmark. Page-level headers and footers can expose banner and contentinfo landmarks; those inside an article or section do not. An <article> describes self-contained content rather than a generic landmark.

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:

HTML
<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.

  1. 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.
  2. 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>.
  3. The main block is the primary content: <main>. "Maya Chen" is the page's topic: <h1>.
  4. "Pottery tutor" is a paragraph: <p>.

Solution.

HTML
<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.

For the next practical step, study accessible forms and validation.

3. Images and alternative text

HTML
<img src="/img/maya-wheel.jpg" alt="Maya guiding a student's hands on a spinning clay pot" width="640" height="427">
  • alt is 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 that provides a link or button name Describe the action: "Search", not "magnifying glass"; use empty alt if adjacent text already supplies the same name
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 width and height attributes (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.

CSS
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

The full cascade considers relevance, origin and importance (including cascade layers), specificity, scope proximity for scoped rules, and finally source order. Transitions and animations also occupy defined priority levels; !important is not a universal trump card.

For the beginner examples below, assume ordinary, unlayered author declarations with no inline styles, animations, transitions, or scoped rules. Within that scope:

  1. Importance and origin: normal author styles beat normal browser defaults; important declarations follow a separate origin and layer order.
  2. Specificity: compare IDs, then classes/attributes/pseudo-classes, then type selectors. Inline declarations have special precedence within their origin and importance; they are not selectors.
  3. Source order: if everything else ties, the later rule wins.
CSS
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

  • px is a CSS pixel, an absolute CSS length unit rather than necessarily one physical screen pixel.
  • rem is relative to the root font size (usually 16px). Prefer rem for text and spacing; users who increase their default font size are respected.
  • em usually refers to the element's own font size; in its font-size property it refers to the inherited parent font size.
  • % uses a reference defined by the property, such as containing-block width for width; it is not universally a percentage of 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:

CSS
.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:

CSS
*, *::before, *::after {
  box-sizing: border-box;
}

Eligible vertical margins in normal block flow can collapse (not between flex or grid items): a 20px bottom margin above a 30px top margin results in a 30px gap, not 50px.

Block and inline

With their normal block display, div, p, h1, and section start on a new line and an auto-width block generally fills available width. CSS can change their display behavior. 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:

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">Tutors</a></li>
      </ul>
    </nav>
  </header>
  <main id="content" tabindex="-1">
    <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:

CSS
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  color: #1a1a1a;
  background: #fff;
}

.skip-link {
  position: absolute;
  left: -999px;
}
.skip-link:focus { left: 1rem; top: 1rem; z-index: 10; background: #fff; padding: .5rem; }

header, main, footer { padding: 1rem; }
.nav-list { display: flex; flex-wrap: wrap; 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 #8a4b00; 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: none for it, because it would then never receive focus. Its tabindex="-1" target can receive focus when the link is activated.
  • max-width: 100%; height: auto keeps the image inside narrow screens.
  • The visible focus outline is retained and made clearer. Removing it with outline: none and no replacement is an accessibility failure.
  • #1f3a5f on 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.
  • Apply aria-current="page" only to the link for the actual current page. This is Maya's profile, so the parent Tutors index is not marked current.
  • tabindex="-1" lets the skip-link destination receive focus without adding it to the normal Tab sequence. Supply the referenced image and linked pages when building this original practice project.

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 study tips

  • Using headings for styling or skipping levels.
  • Using <br> for spacing between paragraphs (use margin).
  • Placing non-phrasing elements such as <div> or <section> inside <p>, which causes parser repair. HTML content rules depend on element categories, not their CSS display value.
  • Missing alt or 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.

Study 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.

Study 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.

  1. 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.
  2. alt="image". It conveys nothing. Replace it with a purposeful description such as "Maya smiling beside a shelf of glazed bowls".
  3. 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