1Flexible Images and Media
▶
One of the most fundamental challenges in responsive web design is ensuring that images, videos, and other embedded media adapt gracefully to screens of every size. A layout that looks pristine on a 1440-pixel-wide desktop monitor can completely break on a 375-pixel-wide phone if the media inside it is fixed in size. Unlike text, which naturally reflows as its container narrows, images and video by default render at their intrinsic pixel dimensions — meaning a 900-pixel-wide photo will simply overflow its container on smaller screens unless specific techniques are applied. This topic covers the full toolkit for making media fluid, from the simplest one-line CSS fix all the way through art direction, embedded iframes, responsive background images, and performance best practices.
The max-width Technique for Images
The most foundational and widely used technique for responsive images requires just two lines of CSS. By setting max-width: 100% on an image, you instruct the browser that the image should never exceed the width of its parent container. If the container is 600px wide and the image's natural size is 900px, the browser will cap the rendered width at 600px. If the container is wider than 900px, the image will simply display at its natural size — it will not stretch beyond its intrinsic dimensions, which would cause ugly pixelation.
Pairing max-width: 100% with height: auto is equally important. Without it, the browser keeps the original height while scaling down the width, squashing the image and distorting its aspect ratio. With height: auto, the height adjusts proportionally as the width changes, preserving the image's original look at every size.
img {
max-width: 100%;
height: auto;
}
Many developers apply this rule globally to all images at the top of their stylesheet as a baseline reset. This technique requires no JavaScript, works in every modern browser, and is considered the minimum acceptable baseline for any responsive project. It does not, however, solve the problem of sending a 2 MB hero image to a device that only needs a 300px thumbnail — for that, more advanced approaches are necessary.
Responsive Images with the srcset Attribute
The srcset attribute on the <img> element allows you to provide a list of candidate image files so the browser can select the most appropriate one based on the device's screen resolution and viewport width. This is a native HTML feature — no JavaScript or server-side logic is required.
There are two descriptor types you can use inside srcset:
- Width descriptors (
w): You declare the actual pixel width of each image file. The browser uses this information, combined with thesizesattribute, to calculate which file is the best match. This is the recommended approach in most scenarios. - Pixel density descriptors (
x): You declare how many device pixels correspond to one CSS pixel. For example,2xtargets high-DPI (Retina) displays. This is simpler to write but gives the browser less information to work with.
Here is a practical example using width descriptors alongside the sizes attribute:
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw,
(max-width: 1024px) 80vw,
60vw"
alt="A scenic mountain landscape"
>
The sizes attribute tells the browser how wide the image will be rendered at each viewport breakpoint — expressed as a CSS length value. In the example above, on viewports up to 600px wide the image will occupy the full viewport width (100vw); between 601px and 1024px it will take up 80% of the viewport; and on larger screens it will be 60% of the viewport. Armed with that layout information and the pixel widths declared in srcset, the browser picks the smallest file that will still look sharp. On a 375px-wide phone screen, for instance, it will download hero-400.jpg instead of the full hero-1200.jpg, dramatically reducing data transfer. On a Retina display it might step up one size to ensure crispness.
The src attribute must always be present as a fallback for browsers that do not support srcset, though such browsers are extremely rare today. The key benefit of this approach is that the browser — not the developer — makes the final selection, and it can factor in connection speed, user preferences, and cached assets in ways that are impossible to predict with server-side logic alone.
Using the picture Element for Art Direction
The srcset technique works well when you want to serve the same image at different sizes. But sometimes a simple scale-down is not enough. Consider a wide landscape photo used as a hero banner: at full width it shows a mountain range with a small person in the center. Scaled down to a phone-sized 375px slot, that person becomes invisible and the image loses its intended impact. In this scenario you want to show a different crop or composition at different breakpoints — perhaps a tightly framed portrait of just the person at small sizes. This is called art direction, and it is solved with the <picture> element.
<picture>
<source
media="(max-width: 480px)"
srcset="hero-portrait-480.jpg"
>
<source
media="(max-width: 1024px)"
srcset="hero-landscape-1024.jpg"
>
<img
src="hero-landscape-full.jpg"
alt="A climber summiting a snow-capped peak"
>
</picture>
The browser evaluates each <source> element in order from top to bottom and uses the first one whose media condition matches the current environment. If none match — or if the browser does not support <picture> at all — it falls back to the <img> element, which must always be present as the last child. This fallback ensures backward compatibility with older browsers while still delivering the best experience to modern ones.
Each <source> can also include its own srcset and sizes attributes, combining art direction with resolution switching for even finer control. The type attribute can additionally be used to specify image formats, allowing you to offer a WebP version to browsers that support it and a JPEG to those that do not:
<picture>
<source
type="image/webp"
srcset="hero.webp 1x, hero@2x.webp 2x"
>
<img
src="hero.jpg"
srcset="hero.jpg 1x, hero@2x.jpg 2x"
alt="Hero image"
>
</picture>
Art direction with <picture> gives designers precise control over which visual story is told at each screen size, going far beyond simple resizing and making it an essential technique for professional responsive design.
Fluid Video and Embedded Media
Native HTML5 <video> elements respond to the same CSS technique used for images. Remove any fixed width and height attributes from the element itself and apply width: 100% and height: auto via CSS:
video {
width: 100%;
height: auto;
}
For even more precise control, the modern CSS aspect-ratio property lets you lock a specific ratio without needing extra wrapper elements:
video {
width: 100%;
aspect-ratio: 16 / 9;
}
This tells the browser to always render the video at exactly a 16:9 ratio regardless of how wide or narrow the container is, eliminating any risk of distortion.
Embedded third-party media — such as YouTube or Vimeo iframes — is significantly more challenging because an <iframe> does not behave like a native media element. Historically, the most reliable solution was the padding-bottom hack, also called the intrinsic ratio technique:
.video-wrapper {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 9 ÷ 16 = 0.5625 = 56.25% */
height: 0;
overflow: hidden;
}
.video-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
<div class="video-wrapper">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
frameborder="0"
allowfullscreen
></iframe>
</div>
The trick works because padding-bottom expressed as a percentage is calculated relative to the element's width, not its height. By setting it to 56.25% (which equals 9 divided by 16), the wrapper's height always equals 56.25% of its own width, locking it to a 16:9 ratio. The iframe is then absolutely positioned to fill the wrapper completely. In modern browsers you can instead use aspect-ratio: 16 / 9 directly on the wrapper and skip the padding hack entirely.
One important caveat: always test embedded third-party iframes thoroughly. External services sometimes inject their own inline width and height styles via JavaScript, which can override your CSS. In such cases, using !important or replacing the default embed code with a facade (a clickable thumbnail that loads the iframe on demand) may be necessary.
CSS background-size for Responsive Background Images
When an image is applied as a CSS background rather than an <img> element, its responsiveness is controlled through the background-size property. There are two primary keyword values:
background-size: cover— Scales the image as large as necessary so that the background area is completely filled. The image's aspect ratio is preserved, and any portion that extends beyond the container is clipped. This is ideal for hero sections and full-bleed banners where covering the entire area matters more than showing the complete image.background-size: contain— Scales the image to the largest size at which both its width and height fit inside the container. The entire image is always visible, but it may leave empty space (letterboxing) if the container's aspect ratio differs from the image's. This is suited for logos, icons, or cases where showing the whole image is essential.
.hero {
background-image: url('hero.jpg');
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
height: 60vh;
}
The background-position property determines which part of the image stays in frame when cover clips it. center center is the most common choice, keeping the focal point of the image visible at all sizes. If your image has an important subject in the upper-left corner, you might use background-position: top left instead.
A powerful complement to CSS background images is combining them with media queries to swap out the image source entirely at different breakpoints:
.hero {
background-image: url('hero-small.jpg');
background-size: cover;
background-position: center;
}
@media (min-width: 768px) {
.hero {
background-image: url('hero-medium.jpg');
}
}
@media (min-width: 1200px) {
.hero {
background-image: url('hero-large.jpg');
}
}
This ensures that a mobile device does not download a massive 3000-pixel-wide hero image it will never fully display. Note that unlike srcset, the browser has no way to intelligently select the best CSS background image — it will always load whichever one the matching media query points to — so thoughtful breakpoint planning is essential.
Performance Considerations for Responsive Media
Flexible layout techniques alone are not sufficient if the underlying media files are poorly optimized. A 4 MB JPEG that scales gracefully to fit any screen still forces every visitor to download 4 MB of data. Performance and responsiveness must be considered together.
The following table summarizes key modern image formats and their characteristics:
| Format | Typical Use Case | Compression Type | Browser Support | Relative File Size vs. JPEG |
|---|---|---|---|---|
| JPEG | Photographs, complex images | Lossy | Universal | Baseline (1×) |
| PNG | Images requiring transparency | Lossless | Universal | Often larger than JPEG |
| WebP | Photographs and graphics with/without transparency | Lossy and lossless | All modern browsers | ~25–35% smaller than JPEG |
| AVIF | High-quality photographs, HDR content | Lossy and lossless | Chrome, Firefox, Safari 16+ | ~50% smaller than JPEG |
| SVG | Icons, logos, illustrations | Vector (resolution-independent) | Universal | Tiny for simple graphics |
Lazy loading is another critical performance tool. By adding the loading="lazy" attribute to an <img> or <iframe> element, you instruct the browser to defer downloading that resource until it is close to entering the viewport. This dramatically reduces initial page load time and data usage for pages with many images:
<img
src="photo.webp"
loading="lazy"
alt="A descriptive alt text"
width="800"
height="600"
>
Note that width and height attributes are included alongside lazy loading. Even though CSS controls the displayed size, these attributes tell the browser the image's intrinsic dimensions before it is downloaded, enabling it to reserve the correct amount of space in the layout and avoid cumulative layout shift (CLS) — a key Core Web Vitals metric.
A principle that is easy to overlook: never rely on CSS alone to make a large image appear smaller. If you have a 3000-pixel-wide image and use CSS to display it at 300px, the browser still downloads every byte of that 3000-pixel file. Always generate and serve a source file that is appropriately dimensioned for its actual use. The ideal workflow is to produce multiple resized exports at image creation time and reference them in srcset, so the browser can download only what it needs.
Finally, pairing responsive images with a Content Delivery Network (CDN) compounds the performance benefits. A CDN caches your image files on servers distributed around the world and serves each request from the node geographically closest to the user, reducing network latency on top of the file-size savings already achieved through proper formatting and sizing. Many modern CDNs also support on-the-fly image transformation, allowing you to request a resized or reformatted version of an image simply by adjusting query parameters in the URL — eliminating the need to manually generate and store multiple copies of every asset.