HTML Media
Multimedia on the web encompasses images, video, audio, and interactive content. HTML provides native elements for embedding each type, with built-in controls, accessibility features, and performance optimizations. Understanding codecs and containers is essential for selecting the right format.
A container format (like MP4 or WebM) holds video, audio, and metadata streams. The codec determines how each stream is compressed. Browser support varies — the safest approach is to provide multiple sources so the browser can choose the first supported format.
| Container | Video Codec | Audio Codec | Browser Support |
|---|---|---|---|
| MP4 | H.264, H.265 (HEVC) | AAC, MP3 | Universal |
| WebM | VP8, VP9, AV1 | Opus, Vorbis | Chrome, Firefox, Edge |
| OGG | Theora | Vorbis, Opus | Firefox, Chrome |
| AVIF | AV1 (intra-frame) | — | Chrome, Firefox, Edge |
| GIF | — | — | Universal (limited colors) |
best practice
The <img> element embeds images into the page. Modern attributes like srcset, sizes, loading, and decoding give developers fine-grained control over responsive behavior and performance.
| Attribute | Values | Description |
|---|---|---|
| src | URL | Image source URL (fallback) |
| alt | string | Alternative text (required for accessibility) |
| srcset | URL w/h descriptor | List of candidate images (e.g., photo-800.jpg 800w) |
| sizes | media query + size | Describes display size for srcset selection |
| loading | lazy | eager | Defers off-screen images (lazy = recommended) |
| decoding | async | sync | auto | Hint for browser image decode timing |
| fetchpriority | high | low | auto | Hint for relative fetch priority |
| width / height | pixels | Intrinsic dimensions (prevents CLS) |
| 1 | <!-- Basic image with all performance attributes --> |
| 2 | <img |
| 3 | src="photo.jpg" |
| 4 | alt="A sunset over the ocean with silhouetted palm trees" |
| 5 | width="1920" |
| 6 | height="1080" |
| 7 | loading="lazy" |
| 8 | decoding="async" |
| 9 | fetchpriority="low" |
| 10 | /> |
| 11 | |
| 12 | <!-- Responsive images with srcset and sizes --> |
| 13 | <img |
| 14 | src="photo-640.jpg" |
| 15 | srcset=" |
| 16 | photo-640.jpg 640w, |
| 17 | photo-800.jpg 800w, |
| 18 | photo-1024.jpg 1024w, |
| 19 | photo-1280.jpg 1280w, |
| 20 | photo-1920.jpg 1920w |
| 21 | " |
| 22 | sizes=" |
| 23 | (max-width: 640px) 100vw, |
| 24 | (max-width: 1024px) 80vw, |
| 25 | 1200px |
| 26 | " |
| 27 | alt="Mountain landscape at dawn" |
| 28 | width="1920" |
| 29 | height="1080" |
| 30 | loading="lazy" |
| 31 | decoding="async" |
| 32 | /> |
| 33 | |
| 34 | <!-- Hero image — eager loading, high priority --> |
| 35 | <img |
| 36 | src="hero.jpg" |
| 37 | alt="Welcome to our platform" |
| 38 | width="1440" |
| 39 | height="600" |
| 40 | loading="eager" |
| 41 | decoding="async" |
| 42 | fetchpriority="high" |
| 43 | /> |
The <picture> element provides art direction and format selection. Multiple <source> elements declare media queries and format types. The <img> inside is always required as the fallback. <figure> with <figcaption> associates a caption with the image.
| 1 | <!-- Format selection with picture element --> |
| 2 | <picture> |
| 3 | <!-- AVIF for modern browsers --> |
| 4 | <source |
| 5 | srcset="photo.avif" |
| 6 | type="image/avif" |
| 7 | /> |
| 8 | <!-- WebP for Chrome/Firefox/Edge --> |
| 9 | <source |
| 10 | srcset="photo.webp" |
| 11 | type="image/webp" |
| 12 | /> |
| 13 | <!-- JPEG fallback --> |
| 14 | <img |
| 15 | src="photo.jpg" |
| 16 | alt="City skyline at night" |
| 17 | width="1200" |
| 18 | height="800" |
| 19 | loading="lazy" |
| 20 | decoding="async" |
| 21 | /> |
| 22 | </picture> |
| 23 | |
| 24 | <!-- Art direction: different image for mobile --> |
| 25 | <picture> |
| 26 | <source |
| 27 | srcset="photo-mobile.jpg" |
| 28 | media="(max-width: 640px)" |
| 29 | /> |
| 30 | <source |
| 31 | srcset="photo-tablet.jpg" |
| 32 | media="(max-width: 1024px)" |
| 33 | /> |
| 34 | <img |
| 35 | src="photo-desktop.jpg" |
| 36 | alt="Product showcase" |
| 37 | width="1920" |
| 38 | height="1080" |
| 39 | loading="eager" |
| 40 | /> |
| 41 | </picture> |
| 42 | |
| 43 | <!-- Figure with caption --> |
| 44 | <figure> |
| 45 | <img |
| 46 | src="architecture.svg" |
| 47 | alt="System architecture diagram showing client-server flow" |
| 48 | width="800" |
| 49 | height="500" |
| 50 | loading="lazy" |
| 51 | /> |
| 52 | <figcaption> |
| 53 | Figure 1: System architecture — clients connect through a load balancer |
| 54 | to application servers backed by a distributed database. |
| 55 | </figcaption> |
| 56 | </figure> |
pro tip
The <video> element embeds video content with native browser controls. Multiple <source> elements provide format fallbacks. The <track> element adds captions, subtitles, and chapters. Text inside the element serves as a fallback for unsupported browsers.
| Attribute | Values | Description |
|---|---|---|
| src | URL | Video source URL |
| controls | boolean | Show native playback controls |
| autoplay | boolean | Start playback on load (requires muted) |
| loop | boolean | Restart video when finished |
| muted | boolean | Start with audio muted |
| poster | URL | Thumbnail shown before playback |
| preload | none | metadata | auto | Hint for preloading behavior |
| playsinline | boolean (iOS) | Play inline without fullscreen on iOS |
| width / height | pixels | Display dimensions (prevents CLS) |
| 1 | <!-- Basic video with controls --> |
| 2 | <video |
| 3 | controls |
| 4 | width="640" |
| 5 | height="360" |
| 6 | poster="thumbnail.jpg" |
| 7 | preload="metadata" |
| 8 | > |
| 9 | <source src="video.mp4" type="video/mp4" /> |
| 10 | <source src="video.webm" type="video/webm" /> |
| 11 | <source src="video.ogg" type="video/ogg" /> |
| 12 | <p>Your browser does not support the video element.</p> |
| 13 | </video> |
| 14 | |
| 15 | <!-- Autoplaying muted background video --> |
| 16 | <video |
| 17 | autoplay |
| 18 | loop |
| 19 | muted |
| 20 | playsinline |
| 21 | poster="bg-fallback.jpg" |
| 22 | width="1920" |
| 23 | height="1080" |
| 24 | preload="auto" |
| 25 | > |
| 26 | <source src="bg-video.mp4" type="video/mp4" /> |
| 27 | <source src="bg-video.webm" type="video/webm" /> |
| 28 | </video> |
| 29 | |
| 30 | <!-- Video with captions track --> |
| 31 | <video |
| 32 | controls |
| 33 | width="640" |
| 34 | height="360" |
| 35 | poster="talk-poster.jpg" |
| 36 | > |
| 37 | <source src="talk.mp4" type="video/mp4" /> |
| 38 | <source src="talk.webm" type="video/webm" /> |
| 39 | |
| 40 | <track |
| 41 | kind="captions" |
| 42 | src="captions-en.vtt" |
| 43 | srclang="en" |
| 44 | label="English" |
| 45 | default |
| 46 | /> |
| 47 | <track |
| 48 | kind="subtitles" |
| 49 | src="subtitles-es.vtt" |
| 50 | srclang="es" |
| 51 | label="Spanish" |
| 52 | /> |
| 53 | <track |
| 54 | kind="chapters" |
| 55 | src="chapters.vtt" |
| 56 | srclang="en" |
| 57 | label="Chapters" |
| 58 | /> |
| 59 | |
| 60 | <p>Your browser does not support video captions.</p> |
| 61 | </video> |
warning
Live preview of a styled video player placeholder:
The <audio> element works like <video> but for sound-only content. It supports the same attributes — controls, autoplay, loop, preload — without the video-specific ones like poster and width/height.
| Attribute | Values | Description |
|---|---|---|
| src | URL | Audio source URL |
| controls | boolean | Show native playback controls |
| autoplay | boolean | Start playback on load (browser restrictions apply) |
| loop | boolean | Restart audio when finished |
| muted | boolean | Start with audio muted (useful with autoplay) |
| preload | none | metadata | auto | Hint for preloading behavior |
| 1 | <!-- Basic audio player --> |
| 2 | <audio |
| 3 | controls |
| 4 | preload="metadata" |
| 5 | > |
| 6 | <source src="podcast.mp3" type="audio/mpeg" /> |
| 7 | <source src="podcast.ogg" type="audio/ogg" /> |
| 8 | <source src="podcast.wav" type="audio/wav" /> |
| 9 | <p>Your browser does not support the audio element.</p> |
| 10 | </audio> |
| 11 | |
| 12 | <!-- Autoplaying background music (muted) --> |
| 13 | <audio |
| 14 | autoplay |
| 15 | loop |
| 16 | muted |
| 17 | preload="auto" |
| 18 | > |
| 19 | <source src="background.mp3" type="audio/mpeg" /> |
| 20 | <source src="background.ogg" type="audio/ogg" /> |
| 21 | </audio> |
| 22 | |
| 23 | <!-- Notification sound (played via JavaScript) --> |
| 24 | <audio |
| 25 | id="notification" |
| 26 | preload="auto" |
| 27 | src="notification.mp3" |
| 28 | ></audio> |
| 29 | |
| 30 | <script> |
| 31 | const notif = document.getElementById('notification'); |
| 32 | function playNotification() { |
| 33 | notif.currentTime = 0; |
| 34 | notif.play().catch(() => { |
| 35 | console.log('Audio blocked — user interaction required'); |
| 36 | }); |
| 37 | } |
| 38 | </script> |
info
Live preview of an audio player placeholder:
The <track> element adds timed text tracks to video and audio elements. It supports captions (dialog + sound effects), subtitles (translated dialog), descriptions (audio description text), chapters (navigation markers), and metadata (custom data). Tracks use the WebVTT format (.vtt).
| Attribute | Values | Description |
|---|---|---|
| kind | captions | subtitles | descriptions | chapters | metadata | Type of timed text |
| src | URL to .vtt file | WebVTT file URL |
| srclang | BCP 47 language tag | Language of the track text |
| label | string | User-visible label for track selection |
| default | boolean | Default enabled track |
| 1 | <!-- WebVTT file format: captions-en.vtt --> |
| 2 | WEBVTT |
| 3 | |
| 4 | 00:00:01.000 --> 00:00:04.000 |
| 5 | Welcome to our tutorial on |
| 6 | HTML media elements. |
| 7 | |
| 8 | 00:00:05.000 --> 00:00:09.500 |
| 9 | Today we will cover images, |
| 10 | video, audio, and captions. |
| 11 | |
| 12 | 00:00:10.000 --> 00:00:14.000 |
| 13 | <v Narrator>First, let's talk about |
| 14 | the <picture> element.</v> |
| 15 | |
| 16 | 00:00:15.000 --> 00:00:20.000 |
| 17 | [camera shutter sound] |
| 18 | |
| 19 | 00:00:22.000 --> 00:00:28.500 |
| 20 | With <c.highlight>srcset</c.highlight> you can |
| 21 | serve different image resolutions. |
| 22 | |
| 23 | <!-- Styling in WebVTT --> |
| 24 | <style> |
| 25 | ::cue(.highlight) { |
| 26 | background: rgba(0, 255, 65, 0.3); |
| 27 | color: #00FF41; |
| 28 | } |
| 29 | </style> |
| 30 | |
| 31 | <!-- HTML with multiple tracks --> |
| 32 | <video |
| 33 | controls |
| 34 | width="640" |
| 35 | height="360" |
| 36 | poster="lecture-poster.jpg" |
| 37 | > |
| 38 | <source src="lecture.mp4" type="video/mp4" /> |
| 39 | <source src="lecture.webm" type="video/webm" /> |
| 40 | |
| 41 | <!-- English captions (dialog + sounds) --> |
| 42 | <track |
| 43 | kind="captions" |
| 44 | src="captions-en.vtt" |
| 45 | srclang="en" |
| 46 | label="English" |
| 47 | default |
| 48 | /> |
| 49 | |
| 50 | <!-- Spanish subtitles --> |
| 51 | <track |
| 52 | kind="subtitles" |
| 53 | src="subtitles-es.vtt" |
| 54 | srclang="es" |
| 55 | label="Español" |
| 56 | /> |
| 57 | |
| 58 | <!-- French subtitles --> |
| 59 | <track |
| 60 | kind="subtitles" |
| 61 | src="subtitles-fr.vtt" |
| 62 | srclang="fr" |
| 63 | label="Français" |
| 64 | /> |
| 65 | |
| 66 | <!-- Chapter markers for navigation --> |
| 67 | <track |
| 68 | kind="chapters" |
| 69 | src="chapters.vtt" |
| 70 | srclang="en" |
| 71 | label="Chapters" |
| 72 | /> |
| 73 | |
| 74 | <!-- Audio description track --> |
| 75 | <track |
| 76 | kind="descriptions" |
| 77 | src="descriptions-en.vtt" |
| 78 | srclang="en" |
| 79 | label="Audio Description" |
| 80 | /> |
| 81 | </video> |
best practice
Responsive media ensures images and videos look great on any screen size. The srcset and sizes attributes handle resolution switching. The <picture> element with media queries handles art direction. CSS properties like object-fit and object-position control how media fills its container.
| Technique | CSS / HTML | Use Case |
|---|---|---|
| Resolution switching | srcset + sizes | Same image, different pixel densities |
| Art direction | <picture> + media | Different crop / composition per breakpoint |
| Format selection | <picture> + type | AVIF/WebP with JPEG fallback |
| Fill container | object-fit: cover | Image/video fills container without distortion |
| Contain | object-fit: contain | Full image visible, letterboxed |
| Aspect ratio | aspect-ratio: 16/9 | Reserve space to prevent CLS |
| 1 | <!-- CSS for responsive media --> |
| 2 | <style> |
| 3 | .responsive-img { |
| 4 | max-width: 100%; |
| 5 | height: auto; |
| 6 | } |
| 7 | |
| 8 | .hero-img { |
| 9 | width: 100%; |
| 10 | height: 60vh; |
| 11 | object-fit: cover; |
| 12 | object-position: center 20%; |
| 13 | } |
| 14 | |
| 15 | .gallery { |
| 16 | display: grid; |
| 17 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
| 18 | gap: 16px; |
| 19 | } |
| 20 | |
| 21 | .gallery img { |
| 22 | width: 100%; |
| 23 | aspect-ratio: 4 / 3; |
| 24 | object-fit: cover; |
| 25 | border-radius: 8px; |
| 26 | } |
| 27 | |
| 28 | .video-container { |
| 29 | width: 100%; |
| 30 | aspect-ratio: 16 / 9; |
| 31 | background: #000; |
| 32 | } |
| 33 | |
| 34 | .video-container video { |
| 35 | width: 100%; |
| 36 | height: 100%; |
| 37 | object-fit: contain; |
| 38 | } |
| 39 | |
| 40 | .background-video { |
| 41 | position: absolute; |
| 42 | width: 100%; |
| 43 | height: 100%; |
| 44 | object-fit: cover; |
| 45 | z-index: -1; |
| 46 | } |
| 47 | </style> |
| 48 | |
| 49 | <!-- Responsive image with art direction --> |
| 50 | <picture> |
| 51 | <!-- Mobile: portrait crop --> |
| 52 | <source |
| 53 | srcset="hero-mobile.jpg" |
| 54 | media="(max-width: 640px)" |
| 55 | /> |
| 56 | <!-- Tablet: square crop --> |
| 57 | <source |
| 58 | srcset="hero-tablet.jpg" |
| 59 | media="(max-width: 1024px)" |
| 60 | /> |
| 61 | <!-- Desktop: full landscape --> |
| 62 | <img |
| 63 | src="hero-desktop.jpg" |
| 64 | alt="Hero banner" |
| 65 | class="hero-img" |
| 66 | loading="eager" |
| 67 | decoding="async" |
| 68 | /> |
| 69 | </picture> |
| 70 | |
| 71 | <!-- Image gallery with consistent aspect ratio --> |
| 72 | <div class="gallery"> |
| 73 | <img |
| 74 | src="photo1.jpg" |
| 75 | alt="Gallery photo 1" |
| 76 | loading="lazy" |
| 77 | decoding="async" |
| 78 | /> |
| 79 | <img |
| 80 | src="photo2.jpg" |
| 81 | alt="Gallery photo 2" |
| 82 | loading="lazy" |
| 83 | decoding="async" |
| 84 | /> |
| 85 | <img |
| 86 | src="photo3.jpg" |
| 87 | alt="Gallery photo 3" |
| 88 | loading="lazy" |
| 89 | decoding="async" |
| 90 | /> |
| 91 | </div> |
pro tip
Media accounts for the majority of page weight. Optimizing image and video delivery is the single highest-impact performance improvement for most websites. Strategies include lazy loading, modern formats, responsive resolution selection, and CDN delivery.
| Technique | Impact | Implementation |
|---|---|---|
| Lazy loading | Defers off-screen images | loading="lazy" |
| Async decode | Non-blocking image decode | decoding="async" |
| Fetch priority | Critical images load first | fetchpriority="high" |
| Modern formats | 40-60% smaller files | AVIF, WebP |
| Responsive srcset | Right size for viewport | srcset + sizes |
| Image CDN | On-the-fly resize + optimization | Cloudinary, Imgix, Next.js Image |
| Video preload | Control video buffering | preload="metadata" |
| Width/height | Prevents CLS | width + height attributes |
| 1 | <!-- Performance-optimized image --> |
| 2 | <img |
| 3 | src="photo-640.jpg" |
| 4 | srcset=" |
| 5 | photo-320.jpg 320w, |
| 6 | photo-640.jpg 640w, |
| 7 | photo-960.jpg 960w, |
| 8 | photo-1280.jpg 1280w, |
| 9 | photo-1920.jpg 1920w |
| 10 | " |
| 11 | sizes=" |
| 12 | (max-width: 400px) 100vw, |
| 13 | (max-width: 800px) 80vw, |
| 14 | 1200px |
| 15 | " |
| 16 | alt="Performance optimized image" |
| 17 | width="1920" |
| 18 | height="1080" |
| 19 | loading="lazy" |
| 20 | decoding="async" |
| 21 | fetchpriority="low" |
| 22 | /> |
| 23 | |
| 24 | <!-- Critical hero image (high priority) --> |
| 25 | <img |
| 26 | src="hero-1920.jpg" |
| 27 | srcset=" |
| 28 | hero-640.jpg 640w, |
| 29 | hero-1024.jpg 1024w, |
| 30 | hero-1920.jpg 1920w |
| 31 | " |
| 32 | sizes="100vw" |
| 33 | alt="Hero banner" |
| 34 | width="1920" |
| 35 | height="800" |
| 36 | loading="eager" |
| 37 | decoding="sync" |
| 38 | fetchpriority="high" |
| 39 | /> |
| 40 | |
| 41 | <!-- Preload critical image in <head> --> |
| 42 | <link |
| 43 | rel="preload" |
| 44 | href="hero-1920.jpg" |
| 45 | as="image" |
| 46 | imagesrcset="hero-640.jpg 640w, hero-1024.jpg 1024w, hero-1920.jpg 1920w" |
| 47 | imagesizes="100vw" |
| 48 | /> |
| 49 | |
| 50 | <!-- Video with metadata preload --> |
| 51 | <video |
| 52 | controls |
| 53 | width="640" |
| 54 | height="360" |
| 55 | poster="thumbnail.jpg" |
| 56 | preload="metadata" |
| 57 | > |
| 58 | <source src="video.mp4" type="video/mp4" /> |
| 59 | </video> |
best practice
Live preview of an optimized responsive image placeholder with CDN-style URL parameters:
Media accessibility ensures users with visual, auditory, or cognitive disabilities can perceive and understand media content. This includes alt text for images, captions and transcripts for audio/video, and proper ARIA labeling for interactive media controls.
| Requirement | Element | Implementation |
|---|---|---|
| Alt text | <img> | Descriptive alt attribute (decorative: alt="") |
| Captions | <video> + <track> | kind="captions" with WebVTT file |
| Transcripts | HTML text | Full text transcript alongside or linked |
| Audio description | <track> | kind="descriptions" narrating visual content |
| aria-label | Buttons, players | Label for custom play/pause controls |
| Figure caption | <figcaption> | Associates descriptive caption with media |
| Reduced motion | CSS / prefers-reduced-motion | Respect user preference to reduce animations |
| 1 | <!-- Accessible image with descriptive alt text --> |
| 2 | <figure> |
| 3 | <img |
| 4 | src="chart.svg" |
| 5 | alt="Bar chart showing revenue growth from $2.1M in Q1 to $3.8M in Q4 2026, with steady upward trend across all four quarters" |
| 6 | width="800" |
| 7 | height="500" |
| 8 | /> |
| 9 | <figcaption> |
| 10 | Figure 2: Annual revenue growth by quarter — 81% increase from Q1 to Q4. |
| 11 | </figcaption> |
| 12 | </figure> |
| 13 | |
| 14 | <!-- Decorative image (empty alt) --> |
| 15 | <img |
| 16 | src="divider.svg" |
| 17 | alt="" |
| 18 | role="presentation" |
| 19 | width="100" |
| 20 | height="20" |
| 21 | /> |
| 22 | |
| 23 | <!-- Accessible video with captions and transcript --> |
| 24 | <video |
| 25 | controls |
| 26 | width="640" |
| 27 | height="360" |
| 28 | aria-label="Tutorial: How to deploy a web application" |
| 29 | > |
| 30 | <source src="deploy-tutorial.mp4" type="video/mp4" /> |
| 31 | <track |
| 32 | kind="captions" |
| 33 | src="deploy-captions.vtt" |
| 34 | srclang="en" |
| 35 | label="English" |
| 36 | default |
| 37 | /> |
| 38 | <p> |
| 39 | Your browser doesn't support video. |
| 40 | <a href="deploy-transcript.html">Read the transcript</a>. |
| 41 | </p> |
| 42 | </video> |
| 43 | |
| 44 | <!-- Transcript link --> |
| 45 | <section aria-label="Video transcript"> |
| 46 | <h3>Transcript</h3> |
| 47 | <p> |
| 48 | In this tutorial, we walk through deploying a web application |
| 49 | to production. First, we configure the build process... |
| 50 | </p> |
| 51 | </section> |
| 52 | |
| 53 | <!-- Custom accessible controls --> |
| 54 | <button |
| 55 | type="button" |
| 56 | aria-label="Play video" |
| 57 | aria-pressed="false" |
| 58 | onclick="togglePlay()" |
| 59 | > |
| 60 | <span aria-hidden="true">▶</span> |
| 61 | </button> |
| 62 | |
| 63 | <!-- Respect reduced motion --> |
| 64 | <style> |
| 65 | @media (prefers-reduced-motion: reduce) { |
| 66 | video.autoplay { |
| 67 | autoplay: none; |
| 68 | } |
| 69 | * { |
| 70 | animation-duration: 0.01ms !important; |
| 71 | transition-duration: 0.01ms !important; |
| 72 | } |
| 73 | } |
| 74 | </style> |
best practice
Format Selection
| Content Type | Recommended Format | Fallback |
|---|---|---|
| Photos | AVIF (best quality/size) or WebP | JPEG |
| Art / Graphics | WebP or PNG | PNG |
| Logos / Icons | SVG | PNG (with @2x for retina) |
| Animated | WebP or AVIF (animated) or MP4 video | GIF (last resort) |
| Video | H.264 MP4 + VP9/AV1 WebM | — |
| Audio | MP3 (broad) or AAC | OGG Vorbis |
Responsive Images Checklist
pro tip
Live preview showing a responsive gallery with proper aspect ratios and object-fit: