Images and Media
img, alt text, figure/figcaption, audio, video, and the source element — plus lazy loading and why alt text is never optional.
img — The Two Attributes That Are Never Optional
<img> is a void element — it has no closing tag and no content between tags, because there is nothing for it to wrap. Everything it needs is expressed through attributes. Two of those attributes are not decoration, not best practice, not "nice to have" — they are the difference between an image that works and one that silently fails: src and alt.
<img src="/images/dashboard-preview.png" alt="Screenshot of the analytics dashboard showing weekly revenue trends">src points the browser at the image file — it follows the exact same relative-vs-absolute path rules you already learned for href in the Links and Navigation module. alt supplies a text alternative for the image: the words a screen reader speaks instead of the picture, the text a browser shows if the image file fails to load, and the content search engines actually index, since they cannot "see" pixels.
What happens when src points at nothing
If the file at src is missing, misspelled, or the server returns a 404, the browser does not simply leave a blank space. It renders a small broken-image icon in the space the image would have occupied, and — critically — it displays the alt text right next to that icon. An image with no alt attribute at all, in this failure case, shows nothing but a bare broken icon with zero context for the person looking at the page. This is the single most concrete, visible reason alt matters even to someone who has never touched a screen reader: it is the fallback content for every image request that fails, and image requests fail constantly, in every production application, from typo'd paths to expired CDN links to a user on a flaky connection.
<!-- No alt: a broken icon, and nothing else, if the file 404s -->
<img src="/images/hero-banner-v2.jpg">
<!-- With alt: the broken icon PLUS readable context -->
<img src="/images/hero-banner-v2.jpg" alt="Team collaborating around a whiteboard in a bright office">alt="" deliberately is a correct, meaningful choice covered in Part 02. Screen readers announce an image with no alt attribute by reading out its filename — IMG_4821_final_v3.jpg spoken aloud is worse than useless, and this happens on real production sites constantly.Meaningful alt vs. Decorative alt=""
Good alt text describes what the image communicates, not what the image literally contains. Think about what a sighted user takes away from glancing at the image, and write that. A product photo's job is to show the product — describe the product, not the photography.
<!-- Weak: describes the file, not the meaning -->
<img src="/products/sneaker-01.jpg" alt="image">
<img src="/products/sneaker-01.jpg" alt="sneaker-01.jpg">
<img src="/products/sneaker-01.jpg" alt="photo of shoe">
<!-- Meaningful: communicates what a sighted user actually sees -->
<img src="/products/sneaker-01.jpg" alt="Red and white running sneaker, side profile, laces untied">The right length depends entirely on context. A product image on an e-commerce grid needs enough detail to distinguish it from the next product — color, style, a defining feature. A decorative background texture needs no description at all, because it carries no information. A chart or infographic needs alt text that summarizes the data trend it shows, since a screen reader user cannot visually scan a bar chart the way a sighted user can.
Decorative images — when alt="" is the correct answer
Not every image carries meaning. A stock photo used purely as visual filler next to a heading that already says everything, a repeating decorative border graphic, an icon that sits right next to text that already states its function — these images add nothing a screen reader user needs to hear. For these, the correct alt value is an explicitly empty string, alt="", never an omitted attribute.
<h1>Our Mission</h1>
<img src="/images/abstract-swirl-decoration.svg" alt="">
<p>We help small businesses grow through better tooling.</p>
<!-- The swirl adds nothing informational. alt="" tells screen readers
to skip it entirely and move straight to the real content. -->The distinction matters because a missing alt attribute and an empty one are treated completely differently by assistive technology. alt="" is an instruction: "this image is decorative, skip it silently." A missing attribute is treated as unlabeled content, and screen readers fall back to announcing the filename or the full image URL — exactly the noisy, meaningless output decorative-image handling is supposed to prevent.
<!-- The trash icon IS the button's entire meaning here — it needs real alt text -->
<button>
<img src="/icons/trash.svg" alt="Delete item">
</button>
<!-- If visible text already labels the action, the icon is decorative -->
<button>
<img src="/icons/trash.svg" alt="">
Delete item
</button>alt="". If information disappears, write alt text that replaces exactly what was lost, no more and no less.width and height — Reserving Space Before the Image Loads
Images almost always load slower than the surrounding text — text renders from HTML the browser already has, while an image is a separate network request that has to complete first. If the browser does not know an image's dimensions ahead of time, it renders the page with zero space reserved for that image, then abruptly shoves everything below it downward the instant the image finishes downloading and its real size becomes known. This visible jump is called layout shift, and it is measured directly as part of Cumulative Layout Shift (CLS) — one of Google's Core Web Vitals, and a real, measured factor in both user experience and search ranking. Full Core Web Vitals coverage comes later, in the Responsive Images & Performance module — the piece that matters right now is the fix.
<img src="/blog/article-hero.jpg" alt="Rows of solar panels on a hillside at sunset">
<!-- The browser has no idea if this is 400px or 4000px tall until the file
downloads. Everything below it jumps once the real height is known. --><img
src="/blog/article-hero.jpg"
alt="Rows of solar panels on a hillside at sunset"
width="1200"
height="675"
>
<!-- The browser now knows the image's ASPECT RATIO (1200:675) before a single
byte of the file has downloaded, and reserves exactly that much space. -->It is a common misunderstanding that width and height here force the image to render at those exact pixel dimensions, breaking responsive layouts. They do not — by default, a browser uses the ratio between the two attributes to compute an aspect ratio, then lets CSS control the actual rendered size. A responsive image styled with img { max-width: 100%; height: auto; } (a rule you will use on nearly every image in a real project, covered fully once you reach CSS) still scales fluidly — the width/height attributes only supply the ratio the browser reserves space with, they do not lock the final rendered size.
The values you put in width and height should match the image's actual intrinsic aspect ratio (its real pixel dimensions, or a proportional value like 1200×675 for a 16:9 image) — not the size you want it to display at. If the ratio is wrong, the reserved space itself will be the wrong shape, and you will get a smaller, different kind of layout shift once the real image settles into place.
figure and figcaption — Grouping Media With Its Caption
<figure> groups a piece of self-contained content — most often an image, but also a code sample, a chart, or a quoted block — together with an optional caption, using <figcaption>. "Self-contained" is the key idea: the content inside a <figure> should make sense on its own, referenced from the main text but not required to sit in one exact spot within it — like a figure in a printed textbook, which a reader can look at before or after the paragraph that mentions it.
<figure>
<img src="/charts/revenue-q3.png" alt="Bar chart showing Q3 revenue up 34% over Q2">
<figcaption>Figure 1 — Quarterly revenue, Q1–Q3 2026</figcaption>
</figure>The relationship between the two elements is strict in one direction only: <figcaption> must be a direct child of <figure> to be meaningful (it can be the first or last child, but it has no standing outside a <figure>), while a <figure> does not require a caption at all — grouping the content alone is already valid and useful.
<figure>
<img src="/team/photo.jpg" alt="The founding team at the company's first office in 2019">
</figure>figure is not only for images
A genuinely common mistake is treating <figure> as an image-only wrapper. Anything that is self-contained and referenceable belongs — a block of example code with a caption explaining it, a pull-quote, an embedded video, even a data table.
<figure>
<pre><code>const total = items.reduce((sum, item) => sum + item.price, 0);</code></pre>
<figcaption>Summing an array of item prices with reduce()</figcaption>
</figure><figure> when a caption belongs to the media, not to the surrounding page. A plain paragraph directly below an image is not a caption in any semantic sense — <figcaption> is what makes that relationship explicit to a screen reader, which will announce it as the caption for the specific figure it belongs to.audio — Native Sound Playback, With Format Fallbacks
<audio> plays sound directly in the browser, with no JavaScript required for basic playback. On its own it renders nothing visible or audible — the controls attribute is what tells the browser to draw its built-in play/pause/volume UI.
<audio controls>
<source src="/podcast/episode-42.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>Not every browser supports every audio codec identically. Rather than picking one format and hoping, the standard pattern is to offer multiple <source> children, each pointing at a different encoded file with its own type. The browser tries each <source> in order and plays the first one it can actually decode, silently skipping any it cannot.
<audio controls>
<source src="/podcast/episode-42.ogg" type="audio/ogg">
<source src="/podcast/episode-42.mp3" type="audio/mpeg">
Your browser does not support the audio element.
<a href="/podcast/episode-42.mp3">Download the audio file instead.</a>
</audio>The plain text (and, here, the fallback download link) between the opening and closing <audio> tags only renders if the browser supports neither <audio> itself nor any of the offered sources — an increasingly rare case on modern browsers, but still worth including as a genuine accessibility and compatibility fallback rather than leaving silent, broken playback controls.
<audio autoplay> element will simply fail to start playback in most modern browsers unless the user has already interacted with the page, or the element is also muted. This is a deliberate browser policy, not a bug — covered in more detail in Part 06, since the same restriction applies to video.video — controls, poster, and the Same source Fallback Pattern
<video> works on the same underlying pattern as <audio> — controls for the built-in playback UI, and one or more <source> children for format fallbacks — plus a few video-specific attributes that matter for real production pages.
<video controls width="1280" height="720" poster="/videos/demo-thumbnail.jpg">
<source src="/videos/product-demo.webm" type="video/webm">
<source src="/videos/product-demo.mp4" type="video/mp4">
Your browser does not support the video element.
</video>poster is an image shown in place of the video before playback starts — without it, some browsers show a blank black frame, or the very first frame of the file, which is not always a flattering or informative thumbnail. Just like <img>, a <video> should carry width and height for the exact same layout-shift reasons covered in Part 03 — video files are typically much larger than images, so the load delay (and the resulting shift, if space is not reserved) is often more noticeable, not less.
track — captions and subtitles
<track> attaches a timed text file (captions, subtitles, or descriptions) to a video, letting a viewer enable captions through the browser's native controls. It is a void element, placed inside <video> alongside the <source> elements.
<video controls width="1280" height="720">
<source src="/videos/product-demo.mp4" type="video/mp4">
<track src="/videos/product-demo.en.vtt" kind="captions" srclang="en" label="English">
</video><video autoplay> tag alone will not play — browsers require the video to also be muted for autoplay to be allowed at all, a policy introduced specifically to stop sites from blasting sound at users the instant a page loads. The reliable autoplay pattern is <video autoplay muted loop playsinline> — and even then, some browsers and some user settings (like reduced-data or reduced-motion preferences) may still block it, so autoplaying video should never be the only way critical content reaches a user.loading="lazy" — A Brief Preview of Deferred Image Loading
The loading attribute tells the browser when to actually fetch an image's file. Setting it to loading="lazy" defers loading any image that is not yet near the visible viewport — the browser only fetches it once the user scrolls close enough that it is about to become visible, instead of downloading every image on the page immediately, whether the user ever scrolls that far or not.
<img
src="/blog/footer-illustration.png"
alt="Illustration of a rocket launching, decorative footer graphic"
width="800"
height="400"
loading="lazy"
>On a long page — a blog post with a dozen images, or a product listing with a hundred thumbnails — this can meaningfully cut how much data loads immediately, and how long the page takes to become interactive. It is a single attribute, native to the browser, requiring no JavaScript library at all.
loading="lazy" only as a preview — the full picture of responsive, performant images (including srcset, sizes, and the <picture> element for serving different image files to different screens) is covered in complete depth in the Responsive Images & Performance module, later in this track. For now, the one rule worth carrying forward: never lazy-load an image that appears above the fold (visible without scrolling) — doing so can actually delay the page's most important visual content and hurt, rather than help, perceived load speed.A Lighthouse Audit at an Austin E-Commerce Startup
A five-person startup building a sneaker resale marketplace is a week from launch. A front-end contractor runs a Lighthouse audit on the product listing page — a grid of 40 sneaker thumbnails — as a routine pre-launch check, expecting a clean pass.
<div class="product-card">
<img src="/products/jordan-1-chicago.jpg">
<h3>Air Jordan 1 Retro High</h3>
<p>$180</p>
</div>What the audit flags
Two failures, repeated across all 40 cards. First, an accessibility violation: every image is missing alt entirely — a screen reader user browsing the grid hears forty filenames read aloud, one after another, with zero indication of which sneaker is which. Second, a Cumulative Layout Shift score in the "poor" range: none of the images specify width or height, so as each of the 40 thumbnails finishes loading at a slightly different moment, the entire grid visibly reflows repeatedly while the page settles — exactly the failure mode from Part 03, just multiplied across every card on the page at once.
<div class="product-card">
<img
src="/products/jordan-1-chicago.jpg"
alt="Air Jordan 1 Retro High in the Chicago colorway, red white and black"
width="600"
height="600"
loading="lazy"
>
<h3>Air Jordan 1 Retro High</h3>
<p>$180</p>
</div>alt text is templated from each product's name and colorway — already structured data sitting in the database — so no manual writing is needed per product. width and height come from the same source, since every product photo is exported at a fixed 600×600 square. loading="lazy" is added everywhere except the first row, which is visible without scrolling on a typical viewport and should load immediately. The re-run audit shows CLS drop from 0.34 (poor) to 0.02 (good), and the accessibility score goes from 61 to 100 — a single templated fix across a component used forty times on one page, and on every future page that reuses the same product card.
Four Misconceptions About Images and Media
5 Interview Questions — With Complete Answers
Image and Media Mistakes Beginners Make Constantly
Errors and Warnings You Will Hit With Images and Media — And Exactly Why
🎯 Key Takeaways
- ✓src and alt are the two non-optional attributes on img — src points at the file, alt supplies a text alternative used by screen readers, search engines, and the failure-case fallback when an image cannot load.
- ✓A missing alt attribute is not the same as an empty one. Omitting it causes screen readers to fall back to reading the filename; alt="" is a deliberate, correct instruction to skip a genuinely decorative image.
- ✓width and height let the browser reserve layout space before an image or video downloads, preventing Cumulative Layout Shift — and they do not conflict with responsive CSS, which still controls the actual rendered size.
- ✓figure groups self-contained, referenceable content (not only images — code, quotes, charts) with an optional figcaption; figcaption only carries meaning as a direct child of figure.
- ✓audio and video both support multiple source children so the browser can pick the first format it can actually decode — the standard way to handle codec support differences across browsers.
- ✓Autoplay with sound is blocked by every major browser. Reliable autoplay requires the muted attribute, and even then should never be the only path to critical content.
- ✓loading="lazy" defers offscreen images until the user scrolls near them — never apply it to above-the-fold content, which should load immediately. Full responsive-image techniques come later, in the Responsive Images & Performance module.
What comes next
Module 06 covers HTML lists — ul, ol, and the often-overlooked dl — including exactly when list order genuinely matters, correct nesting, and the mistakes that produce invalid markup.
Module 06 → Lists — ul, ol, dlDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.