π Beyond Text: Rich Content on the Web
So far, every page you have built uses text: headings, paragraphs, lists, links, and forms. But the web is much richer than that. Spreadsheets become data tables. Photos adapt to every screen with responsive images. Podcasts play with <audio>. Lectures stream with <video>. And entire maps, social posts, and third-party apps appear via <iframe>.
This module teaches you every HTML element for non-text content. Tables that screen readers can navigate. Images that load the right size on every device. Media that plays natively in the browser. And embedded content that stays secure.
π― By the end of this module, you will:
- Build accessible data tables with
<caption>,<thead>,<tbody>, andscope - Merge cells with
colspanandrowspan - Serve the right image size with
srcset,sizes, and<picture> - Embed audio and video with native HTML controls
- Add subtitles to video with the
<track>element - Embed external content safely with
<iframe>andsandbox
π HTML Tables
A table is a grid of data organized into rows and columns. HTML gives you dedicated elements for this: <table> wraps the whole thing, <tr> creates a row, and <td> creates a data cell.
Basic Table Structure
Three elements build every table:
Table Building Blocks
<tr> is one horizontal row.
<table> <tr> <td>Alice</td> <td>92</td> <td>A</td> </tr> <tr> <td>Bob</td> <td>78</td> <td>B+</td> </tr> </table>
Table Headers: <th>
The first row usually contains column headings. Use <th> instead of <td> β the browser renders them bold and centered by default, and screen readers announce them as headers.
<table> <tr> <th>Student</th> <th>Score</th> <th>Grade</th> </tr> <tr> <td>Alice</td> <td>92</td> <td>A</td> </tr> <tr> <td>Bob</td> <td>78</td> <td>B+</td> </tr> </table>
<th> to announce column context. When a blind user navigates to "92", the screen reader says "Score: 92" β but only if you used <th>. With plain <td>, it just says "92" with no context.
ποΈ Advanced Tables
Real-world tables need more than rows and cells. A good data table has a caption (title), header/body/footer sections, cells that span multiple columns or rows, and scope attributes for accessibility.
Caption: The Table Title
The <caption> element gives the table an accessible, visible title. It must be the first child of <table>. Screen readers announce it before reading the table data.
<table> <caption>Q3 2024 Sales Report</caption> <tr> <th>Region</th> <th>Revenue</th> </tr> <!-- rows... --> </table>
Table Sections: thead, tbody, tfoot
Group your rows into logical sections. This helps the browser (it can scroll the body while keeping the header fixed), helps CSS (you can style each section differently), and helps screen readers (they know which rows are headers vs data).
Table Section Elements
<tr> with <th> cells.
<table> <caption>Monthly Expenses</caption> <thead> <tr> <th scope="col">Category</th> <th scope="col">January</th> <th scope="col">February</th> </tr> </thead> <tbody> <tr> <th scope="row">Rent</th> <td>$1200</td> <td>$1200</td> </tr> <tr> <th scope="row">Food</th> <td>$350</td> <td>$420</td> </tr> </tbody> <tfoot> <tr> <th scope="row">Total</th> <td>$1550</td> <td>$1620</td> </tr> </tfoot> </table>
Spanning: colspan & rowspan
Sometimes a cell needs to stretch across multiple columns or rows. Use colspan to merge horizontally and rowspan to merge vertically.
<tr> <td colspan="3">Spans 3 columns</td> </tr>
The cell takes the width of 3 normal cells. The row needs fewer <td> elements to compensate.
<td rowspan="2"> Spans 2 rows </td>
The cell stretches down into the next row. The row below needs one fewer <td> in that column.
Scope: Accessibility for Complex Tables
When a sighted person reads a table, they glance at the top row and left column to understand what each cell means. A screen reader can't "glance" β it reads cells one by one. The scope attribute is how you tell it which header belongs to which cells.
There are two values: scope="col" means "I am a header for all the cells below me in this column", and scope="row" means "I am a header for all the cells to the right of me in this row".
"I label everything below me"
"I label everything to my right"
Why does this matter? Consider this table β a screen reader user navigates to the highlighted cell. What does it mean?
| Category | January | February |
|---|---|---|
| Rent | $1200 | $1200 |
| Food | $350 | $420 |
| Total | $1550 | $1620 |
<table> <caption>Monthly Expenses</caption> <thead> <tr> <th scope="col">Category</th> <th scope="col">January</th> <th scope="col">February</th> </tr> </thead> <tbody> <tr> <th scope="row">Rent</th> <td>$1200</td> <td>$1200</td> </tr> <tr> <th scope="row">Food</th> <td>$350</td> <td>$420</td> </tr> </tbody> <tfoot> <tr> <th scope="row">Total</th> <td>$1550</td> <td>$1620</td> </tr> </tfoot> </table>
βοΈ A screen reader lands on the highlighted cell. What should it announce?
Every <th> in a <thead> gets scope="col". Every <th> that starts a data row gets scope="row". That's it β two values, always on <th>, never on <td>.
πΌοΈ Responsive Images
A basic <img> loads the same file on every device. A 2000px hero image downloads fully on a 320px phone β wasting bandwidth, slowing the page, and draining battery. Responsive images let the browser choose the best file for the current screen.
Never use absolute local paths like /Users/name/Desktop/photo.jpg or C:\Users\name\photo.jpg in your HTML. They only work on your computer β deploy to a server and everything breaks because the server has a completely different file structure. Use only:
- Relative paths β
images/photo.jpgβ relative to your HTML file's location - Full external URLs β
https://example.com/photo.jpgβ works from anywhere on any server
In this module, code examples use full external URLs so you can test them directly with "Try it β".
srcset: Resolution Switching
The srcset attribute provides a list of image files with their widths. The browser picks the best one based on the viewport and pixel density.
<img src="https://picsum.photos/id/29/800/600" srcset="https://picsum.photos/id/29/400/300 400w, https://picsum.photos/id/29/800/600 800w, https://picsum.photos/id/29/1200/900 1200w" sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw" alt="A mountain landscape" >
β This image uses srcset with 3 sizes: 400px, 800px, and 1200px. Open DevTools β Network tab and resize to see the browser pick the optimal file.
How srcset + sizes Work Together
srcset.
400w means the file is 400px wide).
The <picture> Element: Art Direction
Sometimes you don't just want a smaller version β you want a different image entirely. A wide landscape shot for desktop, a square crop for mobile. The <picture> element gives you full control.
<picture> <source media="(min-width: 800px)" srcset="https://picsum.photos/id/1015/1200/400" > <source media="(min-width: 400px)" srcset="https://picsum.photos/id/1015/600/600" > <img src="https://picsum.photos/id/1015/400/600" alt="Product showcase" > </picture>
β Wide panoramic on desktop, square on tablet, tall portrait on mobile. Three different crops of the same scene β that is art direction with <picture>.
The browser evaluates <source> elements from top to bottom and uses the first one whose media query matches. The <img> at the bottom is the mandatory fallback β <picture> does not work without it.
<picture> with <source media="..."> β full art direction control
<img srcset="..." sizes="..."> β browser picks the best size
<img src="..." alt="..."> β simple and effective
<picture> to serve modern formats like WebP or AVIF with a JPEG fallback: <source type="image/webp" srcset="photo.webp">. These formats are 25β50% smaller with the same quality.
π Audio
The <audio> element embeds sound directly in the page β no plugins, no Flash, no JavaScript. Add the controls attribute and the browser shows a native play button, progress bar, and volume slider.
<audio controls> <source src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" type="audio/mpeg"> Your browser does not support the audio element. </audio>
Audio Attributes
muted. Avoid it β unexpected sound annoys users.
"none" (don't load), "metadata" (load duration only), or "auto" (load the whole file). Default varies by browser.
Audio Formats
Not every browser supports every format. Provide multiple <source> elements as fallbacks β the browser uses the first one it supports.
π¬ Video
The <video> element works like <audio> but with a visual component. It supports multiple sources, a poster image (thumbnail), and β critically β the <track> element for subtitles and captions.
<video controls width="640" height="360" poster="https://picsum.photos/id/180/640/360" > <source src="https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" type="video/mp4"> Your browser does not support the video element. </video>
Video Attributes
Subtitles & Captions: The <track> Element
The <track> element adds text tracks (subtitles, captions, descriptions) to a video. It references a WebVTT file (.vtt) β a simple text format with timestamps.
<video controls width="640" height="360"> <source src="https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" type="video/mp4"> <track src="assets/captions-en.vtt" kind="captions" srclang="en" label="English" default > <track src="assets/subtitles-fr.vtt" kind="subtitles" srclang="fr" label="FranΓ§ais" > </video>
β Click the CC button in the video controls to toggle captions. Use the settings gear to switch between English captions and French subtitles. The .vtt files are in the assets/ folder.
kind="captions") β Include all sounds: dialogue, music, sound effects. For deaf/hard-of-hearing users.
kind="subtitles") β Translate dialogue only into another language. Assume the viewer can hear.
WEBVTT 00:00:00.500 --> 00:00:02.000 Introducing Chromecast. 00:00:02.500 --> 00:00:06.000 The easiest way to enjoy online video and music on your TV. 00:00:06.500 --> 00:00:10.000 For when Batman's escapes aren't quite big enough. 00:00:11.000 --> 00:00:13.000 [music] For $35.
πͺ Iframes & Embedding
An <iframe> embeds an entire web page inside your page β like a window into another site. YouTube videos, Google Maps, CodePen demos, and social media posts all use iframes.
<iframe src="https://en.wikipedia.org/wiki/HTML" width="560" height="315" title="Wikipedia article about HTML" loading="lazy" allowfullscreen ></iframe>
Key Iframe Attributes
/embed/ URL, not the regular watch URL.
Embedding Google Maps
Google Maps provides an embed URL that you drop into an iframe. Go to Google Maps β Share β Embed a map β copy the iframe code.
<iframe src="https://www.google.com/maps/embed?pb=!1m18..." width="600" height="450" style="border:0" allowfullscreen loading="lazy" title="Office location on Google Maps" ></iframe>
π Iframe Security
An iframe runs code from another website inside your page. This is a security risk. The sandbox attribute restricts what the iframe can do.
<iframe src="..."> </iframe>
The embedded page can run scripts, submit forms, navigate your page, access cookies, and more.
<iframe src="..." sandbox="allow-scripts allow-same-origin" ></iframe>
Only explicitly allowed capabilities work. No form submission, no popups, no top-level navigation.
Sandbox Values
allow-scripts and allow-same-origin on an iframe pointing to a domain you don't trust β the embedded page could remove the sandbox entirely. Only use both together for trusted sources (YouTube, Google Maps, your own domains).
βοΈ Practice: Tables, Media & Embedding
Try each exercise mentally first, then reveal the solution to check your understanding.
Exercise 1: Build an Accessible Table
Create a table showing a class schedule: 3 columns (Day, Subject, Time) and 3 data rows. Include a caption, use <thead>/<tbody>, and add scope to every <th>.
<table> <caption>Weekly Class Schedule</caption> <thead> <tr> <th scope="col">Day</th> <th scope="col">Subject</th> <th scope="col">Time</th> </tr> </thead> <tbody> <tr> <th scope="row">Monday</th> <td>Mathematics</td> <td>9:00 β 10:30</td> </tr> <tr> <th scope="row">Wednesday</th> <td>Physics</td> <td>11:00 β 12:30</td> </tr> <tr> <th scope="row">Friday</th> <td>Chemistry</td> <td>14:00 β 15:30</td> </tr> </tbody> </table>
Exercise 2: Choose the Right Element
For each scenario, which HTML element or attribute should you use?
1. You need a hero image that loads a 400px version on phones and a 1200px version on desktops.
β<img srcset="small.jpg 400w, large.jpg 1200w" sizes="100vw"> β same image, different resolutions. Use relative paths or full URLs.
2. You want a landscape photo on desktop but a portrait crop on mobile.
β<picture> with <source media="..."> β art direction (different crops)
3. You want to embed a podcast that users can play/pause.
β<audio controls><source src="..." type="audio/mpeg"></audio>
4. You have a lecture video and need French subtitles.
β<track src="subs-fr.vtt" kind="subtitles" srclang="fr" label="FranΓ§ais"> inside a <video>
5. You need to show a comparison of quarterly sales in rows and columns.
β An HTML<table> with <caption>, <thead>, <tbody>, and scope on all <th> elements
Exercise 3: Spot the Iframe Issues
This iframe embeds a third-party widget. Find all the problems.
<iframe src="https://untrusted-widgets.example.com/chat" width="400" height="300" ></iframe>
β No title attribute β screen readers have no description of the iframe content. Add title="Customer chat widget".
β No sandbox β an untrusted third party gets full access to run scripts, submit forms, and navigate your page. Add sandbox="allow-scripts".
β No loading="lazy" β the iframe loads immediately even if it's below the fold, slowing your page. Add loading="lazy".
π‘ Consider adding referrerpolicy="no-referrer" so the embedded site doesn't know which page loaded it.
Exercise 4: Fix the Broken Video
This video element has multiple problems. Find and fix them all.
<video autoplay> <source src="/Users/mehdi/Desktop/lecture.mp4"> </video>
β Absolute local path β /Users/mehdi/Desktop/lecture.mp4 only works on one computer. On any server this breaks. Use a relative path (videos/lecture.mp4) or a full URL (https://...).
β No controls β without it, the video has no play button, no seek bar, no volume slider. The user cannot interact at all.
β autoplay without muted β browsers block autoplaying video with sound. Either remove autoplay or add muted.
β No type attribute on <source> β the browser must download the file to check the format. Add type="video/mp4".
β No width/height β causes layout shift when the video loads. Always set dimensions.
β No <track> for captions β inaccessible to deaf and hard-of-hearing users. Add at least one caption track.
β No fallback text β older browsers show nothing. Add text like "Your browser does not support video."
π Module Summary
<table> with <tr>, <th>, <td>. Use <caption> for a title, <thead>/<tbody>/<tfoot> for structure, and scope on every <th> for accessibility.
colspan merges cells horizontally, rowspan merges vertically. Adjust the number of cells in affected rows to compensate.
srcset + sizes for resolution switching. <picture> + <source media> for art direction. Modern formats (WebP, AVIF) via <source type>.
<audio controls> with <source> for format fallback. Always include controls. Avoid autoplay β browsers block it anyway.
<video controls poster="..."> with <track kind="captions"> for accessibility. Set width/height to prevent layout shift.
Embed YouTube, Maps, and third-party content with <iframe>. Always add title, loading="lazy", and sandbox for untrusted sources.