Why this session exists
After Session 2 your page has real words, working links, and a finished head. But it is still a text file that looks like one, and under the hood it is one long body with no map. Today it gets media (Part 1 and 2) and a semantic skeleton (Part 3) - the same landmark structure every professional site and every screen reader relies on.
Images done right 8 min live
One void element, four attributes, and a habit of honesty. The img element is easy to use and easy to use badly - the difference is almost entirely in the alt text and in telling the browser the image's size up front.
Liveimg - src, alt, width, height3 min▶
<img> is a void element (no content, no closing tag - Session 1) that is all attributes: src says which file, alt says what it shows, width and height declare its natural size in pixels so the browser reserves the space before the file arrives. Skip the size and the page jumps around while loading - the layout shift you feel on janky news sites.
<p>A photo, politely announced:</p>
<img src="https://picsum.photos/300/200"
alt="Placeholder landscape photo from the picsum service"
width="300" height="200">
- The demo URL is a placeholder service - picsum.photos returns a random photo at the size you ask for. Handy for layouts; your real page uses your real files.
- Width and height set the real ratio; making images respond to screen size is a CSS job (Session 5), and serving different files to different screens (srcset) is a deep dive for 6.3.
LiveThe alt discipline - describe function, not filename2 min▶
The alt attribute is what a screen reader speaks, what renders if the file fails, and what search engines index. The test: if the image vanished, what words would do its job? Describe what it means in context, never what the file is called.
- Decorative images get
alt=""- empty, but present. That tells screen readers "skip me, I am decoration". Omitting alt entirely makes them read the filename aloud instead. - Never start with "image of" - the screen reader already announces it is an image.
Data folks: alt text is your chart's caption for machines. Dashboards and reports get emailed, and email clients block images by default - the alt text is often the only thing stakeholders see on first open. A one-line summary of the finding beats a broken-image icon every time.
Livefigure and figcaption - images that explain themselves2 min▶
When an image needs a visible caption, wrap both in <figure> and put the caption in <figcaption>. The browser now knows the caption belongs to the image - they travel together, and assistive tech announces them as one unit. Note the two are different jobs: alt describes the image, the caption comments on it.
<figure>
<img src="https://picsum.photos/400/220"
alt="Placeholder photo standing in for a project screenshot"
width="400" height="220">
<figcaption>Fig 1 - the caption travels with the image.</figcaption>
</figure>
Figure is not only for images: a code block, a chart, or a table with a caption are all figures. If it could move to an appendix and still make sense with its caption, it is a figure.
Self-studyWhere files live, which format, and whose picture it is3 min read▶
Three housekeeping habits that separate tidy sites from junk drawers:
- An images/ subfolder. Inside
my-site, next toindex.html, make a folder calledimagesand keep every picture there. The src is then a relative path, exactly like the link paths from Session 2:src="images/me.jpg". - Lowercase, no spaces in filenames:
phoebe-portrait.jpg, neverMy Photo (1).JPG. Servers are case-sensitive and spaces break URLs.
| Format | One-line rule |
|---|---|
.jpg | Photos - small files, no transparency, slight quality loss. |
.png | Screenshots and images needing transparency - lossless, bigger files. |
.webp | The modern all-rounder - smaller than both, supported everywhere that matters. |
.svg | Logos, icons, diagrams - drawn from text, sharp at any size (the diagrams on this page are SVG). |
Audio and video - media with manners 5 min live
The video and audio elements follow the img mental model, with two upgrades: they can offer the browser several file formats to choose from, and they hold fallback content for the rare browser that understands neither.
Livevideo - controls, source elements, fallback3 min▶
The pattern: a <video> element with controls (never autoplay a visitor's speakers), a poster image to show before play, one or more <source> children listing formats from best to safest, and plain fallback content at the bottom. The browser plays the first source it supports and shows the fallback only if it supports none.
<video controls width="320" height="180"
poster="images/intro-poster.jpg">
<source src="images/intro.webm" type="video/webm">
<source src="images/intro.mp4" type="video/mp4">
Your browser does not play video.
<a href="images/intro.mp4">Download the file instead.</a>
</video>
- controls is a boolean attribute - present means on, no value needed. It gives you play, pause, volume, and fullscreen for free.
- poster is the cover image before play - without it, visitors stare at a black rectangle.
- Captions: real published video should carry a
<track>element pointing at a captions file. We mark it honest-incomplete today (◐) - the pattern is one line, writing good captions is the real work.
Liveaudio - the same idea, minus the picture2 min▶
<audio> works exactly like video without width, height, or poster. Same controls attribute, same source-and-fallback pattern.
<p>Episode 1 of my someday-podcast:</p> <audio controls src="audio/episode-1.mp3"> Your browser does not play audio. </audio>
This is the exact markup behind podcast pages, voice-note features, and every "listen to this article" button. And the multi-source pattern is a data lesson in disguise: ship formats in preference order, let the client pick the first one it can handle - the same graceful-degradation thinking as good API design.
Semantic layout - give the page a map 7 min live
So far everything sits loose in the body. Semantic layout elements wrap your content in named regions - landmarks that screen readers jump between, search engines weigh, and future-you navigates. Same page, now with a map legend.
Liveheader, nav, main, footer - the big four3 min▶
<header>- the introduction strip: your name (the h1) and the nav. Not to be confused with the invisible<head>from Session 1 - header is visible content.<nav>- wraps your list of links from Session 2. The wrapper is what lets assistive tech announce "navigation" and offer to skip it.<main>- the unique content of this page. The one hard rule of the day: exactly one main per page, and it never holds the header or footer.<footer>- the sign-off: copyright line, contact, small print.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Phoebe Fu</title>
</head>
<body>
<header>
<h1>Phoebe Fu</h1>
<nav>
<ul>
<li><a href="#about">About</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<section id="about">
<h2>About</h2>
<p>Data and AI person, building a corner of the internet.</p>
</section>
<section id="projects">
<h2>Projects</h2>
<p>Things shipped and things brewing.</p>
</section>
<section id="contact">
<h2>Contact</h2>
<p>Say hello.</p>
</section>
</main>
<footer>
<p>Built by hand, one session at a time.</p>
</footer>
</body>
</html>
Livesection vs article vs div - the decision rules3 min▶
Three wrappers, three questions, asked in order:
- Would it make sense on its own, somewhere else? A blog post, a product card, a project entry that could appear in a feed - that is an
<article>. Self-contained is the test. - Is it a themed chunk of this page, deserving its own heading? About, Projects, Contact - that is a
<section>. A section without a heading is usually a section that should not exist. - Neither - you just need a box to style or group? That is a
<div>. No meaning, no shame; it is the honest choice when no semantic element fits.
And <aside> is for content related to, but separable from, the main flow: a "currently reading" box, a related-links list. Nesting is normal: articles inside sections, sections inside articles - the questions above decide each layer.
Self-studydiv, span, and the outline nuances2 min read▶
<div> is the meaningless block, <span> its inline sibling - a way to grab a few words inside a sentence, which becomes gold in Session 5 when classes and CSS arrive. Reach for them last, not first.
One nuance from web.dev's "Headings and sections" worth knowing: browsers do not build a real document outline from nested sections - your h1-h6 ladder from Session 1 is still what defines the outline. So keep the ladder honest inside your regions: one h1 in the header, h2 per section. Regions are the map, headings are the index.
Wrap your page in regions ★ 8 min · follow along
Your index.html has content and a nav list. Nothing new gets written today - the existing content gets wrapped in the four regions, and the page becomes navigable by machine.
Open your index.html. Wrap your h1 and the nav list from Session 2 in a <header> element.
Wrap everything between the header and the bottom of the page in a single <main>. One per page - this is it.
Inside main, wrap each h2 block in a <section> and move the id from the heading onto the section: <section id="about">, <section id="projects">, <section id="contact">. Your anchor links keep working - ids are ids.
After main, add a <footer> with one honest paragraph: your name, the year, how to reach you.
Save, reload, and observe: the page looks identical. That is correct - semantics change what the page IS, not how it looks. Session 5 pays this off when regions become styling hooks.
Your photo, honestly captioned ★ 5 min · build your own
The page gets a face - yours, or anything that represents you. This is the moment the images/ folder is born.
Create an images folder next to your index.html. Drop in a photo, renamed to something clean: me.jpg or phoebe-portrait.jpg. Lowercase, no spaces.
In your About section, add the image: <img src="images/me.jpg" alt="..." width="..." height="...">. Get the real pixel size from your file manager's info panel.
Write the alt like a pro: what would a visitor need to know if the photo never loaded? "Phoebe smiling on a mountain trail" beats "photo of me".
Wrap it in a <figure> and add a <figcaption> with a visible one-liner. Save, reload - if you see a broken icon, check the path and the filename spelling first, exactly like Session 2's link debugging.
Relative paths bite here the same way they bite in notebooks: src="images/me.jpg" means "from where THIS file sits". Move index.html without moving the folder and the image breaks - the same reason your pandas read_csv("data/file.csv") breaks when you run the notebook from a different directory.
The landmark tour ★ 4 min · follow along
See your page the way a screen reader sees it. Devtools has an accessibility view that renders the landmark structure you just built - proof that semantics are real, not ceremony.
On your page, right-click anywhere and choose Inspect. In devtools, find the Accessibility panel (Chrome: it is a tab near Styles; Firefox: its own top-level tab).
Look at the accessibility tree: your header appears as banner, nav as navigation, main as main, footer as contentinfo. Those are the landmarks a screen-reader user jumps between.
Now inspect a plain div on any other site and compare: no landmark, no name, invisible to the tour. That is the difference you built today.
Find your image in the tree and confirm your alt text is what it announces. If you used alt="" anywhere, watch that image vanish from the tree entirely - as designed.
Try it yourself - this week ◐ 30-45 min total
- Add two more images to your Projects section - a screenshot or photo per project entry, each with honest alt, proper size attributes, and a figure/figcaption where a caption helps.
- Alt audit: for each image on your page, cover it with your hand and read only the alt. Does the page still make its point? Rewrite the ones that fail.
- Landmark-read three real sites (a news site, a docs site, a dashboard): open the accessibility tree and sketch their region map. Notice how consistent the pattern is.
- Optional media: record a 20-second hello (voice memo works), export as .mp3 into an
audiofolder, and embed it with the audio pattern from Part 2. - Bring to Session 4: one table of data you actually care about (a CSV, a spreadsheet corner, a league standing) - it becomes your first real HTML table.
Official sources covered
This session teaches the working content of these lessons from MDN's Structuring content with HTML and web.dev Learn HTML. Their interactive challenges stay with the originals - our exercises are equivalents, not copies.
Three questions before you go 🎯 ◐ 90 seconds
1 · A purely decorative divider image sits between two sections. The right alt is...
Empty alt tells screen readers to skip decoration. Describing it adds noise; omitting alt entirely makes some readers announce the filename instead.
2 · Each project entry on your page could also appear on its own in a portfolio feed. The best wrapper is...
Self-contained is the article test. Section is for themed chunks of THIS page; div is for when no meaning applies at all.
3 · How many main elements may a page have?
One main per page - it marks THE unique content. Screen readers offer "skip to main content" and there must be one unambiguous answer.