A designer hands over a gorgeous hero photograph, 4000 pixels wide and 3 MB in size. A developer drops it into the page, scales it down with CSS, and moves on. On a large monitor with a fast connection it looks perfect. On a mid-range phone over a congested network, the page sits half blank for several seconds, and then the text jumps down the screen when the picture finally arrives.
Both of those failures have the same root cause. The browser was never told enough about the image to make a good decision, so it either downloaded far too much or guessed wrong about how much room to save. Fixing it is not complicated, but it does require treating images as a layout concern and not just a content one.

Photo by Viktorya Sergeeva š« on Pexels
The Two Problems Responsive Images Must Solve
Every image on a page creates two separate problems, and it is worth keeping them apart. The first is a bandwidth problem: how many bytes does the visitor download, and are those bytes appropriate for the size of the screen? The second is a stability problem: does the page know how much space the image will occupy before the bytes arrive?
Most tutorials cover only the first. They explain how to offer multiple file sizes and stop there. The result is a page that downloads efficiently and still scores badly on layout stability, because the browser reserved no space and the content reflowed when the image appeared.
A good implementation handles both at once. The markup that tells the browser which file to fetch is closely related to the markup that tells it what shape the image will be, so there is no reason to treat them as separate projects.
Start With Honest Dimensions
The single most valuable habit is adding width and height attributes to every image element. These are not styling instructions in the modern sense. Browsers use the pair to calculate an aspect ratio before the file loads, and they reserve a box of that ratio in the layout.
Set the attributes to the intrinsic pixel dimensions of the file you are serving, then let CSS control the displayed size. A rule such as max-width: 100% combined with height: auto lets the image shrink on narrow screens while keeping the reserved ratio intact. Without the attributes, the browser knows nothing until the headers of the image arrive.
For images that must fill a container of a fixed shape, the CSS aspect-ratio property is a clean alternative. It is useful for thumbnails, cards, and gallery tiles where crops are consistent. Pair it with object-fit: cover so that a photograph with a different native shape fills the box without distortion.
Let the Browser Choose With srcset and sizes
The srcset attribute lists several versions of the same image, each with its true width. The sizes attribute tells the browser how wide the image will actually be displayed at different viewport widths. With both, the browser can pick the smallest adequate file for the current screen and pixel density.
The mistake people make most often is omitting sizes or leaving it at the default. When sizes is missing, the browser assumes the image fills the entire viewport width, and it picks an oversized file even when the image sits in a narrow column. If a card occupies a third of a 1200 pixel layout, say so in the attribute.
A practical starting set of widths is 400, 800, 1200, and 1600 pixels. That range covers most phones, tablets, and laptops without generating a huge library of files. Add a 2400 pixel version only for full-bleed hero images that really do span a wide, high-density display. The MDN documentation on responsive images walks through the syntax in detail if you want the exact attribute grammar.
Use the picture Element for Art Direction
Sometimes a smaller version of an image is not simply a scaled copy. A wide banner showing a product on a desk might need a tighter, square crop on a phone so the product stays legible. This is called art direction, and it is what the picture element is for.
Inside picture, you list source elements with media conditions, and each one points to a differently cropped file. The img element at the end acts as the fallback and carries the alt text, width, and height. Because the crops have different shapes, remember that the reserved aspect ratio must match whichever source the browser selects.
Use picture sparingly. If you only need different sizes of the same composition, srcset is simpler and easier to maintain. Reach for art direction when a plain downscale would hide the subject or make the text inside the image unreadable.

Photo by Trash Art on Pexels
Choose Formats Deliberately
File format matters as much as pixel dimensions. Modern formats such as WebP and AVIF compress photographs far better than JPEG at similar visual quality, and the savings on a photo-heavy page can be substantial. You can check current browser coverage on Can I use before deciding how much fallback you need.
The picture element also handles format negotiation. List an AVIF source first, a WebP source second, and a JPEG fallback in the img element. Each browser takes the first format it understands and ignores the rest, so older browsers never see a broken image.
Match format to content. Photographs suit lossy formats. Logos, diagrams, and interface screenshots with flat color and sharp edges usually do better as SVG or as PNG, since lossy compression leaves visible smudges around text. A free tool like Squoosh lets you compare formats and quality settings side by side on one of your own images before you commit to a pipeline.
Load Lazily, but Never the Image Above the Fold
Adding loading="lazy" tells the browser to postpone fetching an image until it is near the viewport. It is one attribute, it needs no JavaScript, and on long pages it can cut the initial download dramatically. Apply it to everything that begins below the first screen.
Do not apply it to the main image visible on load. A lazy hero image delays the largest element on the page, which hurts the loading metrics you were trying to improve. For that single image, do the opposite: leave loading at the default and raise its priority with fetchpriority="high", so the browser starts the request early.
Also set decoding="async" on large images so decoding does not block the main thread while text is being painted. The three attributes together, used in the right places, do more for perceived speed than most performance plugins.
Keep Placeholders Honest
A reserved empty box is better than a jump, but a blank rectangle is not a great experience either. Many teams add a dominant-color background or a tiny blurred preview to the container so the area feels intentional while the real file arrives.
Keep these placeholders cheap. A solid background color set from a value stored with the image costs nothing to deliver. A blurred preview should be a few hundred bytes, inlined, and swapped out when the full image decodes. If the placeholder itself is a heavy file, you have recreated the problem you were solving.
"Teams tend to treat image performance as a compression task. The bigger win is usually telling the browser the image's shape and display size up front, because that fixes both the bytes and the jumping at the same time." - Dennis Traina, founder of 137Foundry
Build a Repeatable Pipeline
Doing all of this by hand for each photograph does not scale, and handwritten srcset strings drift out of date. The sustainable approach is to generate variants automatically at upload or build time and have templates output the full markup from a single source image.
Most content systems and static site generators have a plugin or helper that resizes, converts, and writes the attributes for you. Whatever you choose, make sure it records the intrinsic width and height of each variant, because those values feed the layout reservation. Store the original at the highest quality you have, so you can regenerate everything when formats improve.
Add alt text to the content workflow rather than the template. The text belongs with the image, not with the component that displays it, and a required field at upload prevents the empty alt attribute that quietly slips past review.
Test on Real Conditions
Measurement closes the loop. Run a lab audit with the tools described on web.dev, then confirm against field data to see what real visitors experience. Pay attention to the Largest Contentful Paint element and to the layout shift entries, since images are the most common cause of both.
Test with throttled network and a small viewport, not just a desktop window. Open the network panel and check which file each image actually downloaded. If a 1600 pixel file is loading on a 390 pixel screen, your sizes attribute is wrong or missing, and the fix will be obvious once you see it.
Repeat the check after template changes. Image regressions creep in through new components, third-party embeds, and marketing swaps of hero banners that skip the pipeline. A short checklist in the review process catches most of them before they ship.
Closing Note
Responsive images are not a single feature but a small set of agreements with the browser: here is the shape, here are the sizes, here is the format, and here is how urgent it is. When those agreements are written down in the markup, pages load lighter and stay still while they do it.
If your team wants help auditing image delivery or rebuilding a media pipeline, the web development service at 137Foundry covers performance work like this, and the services hub outlines the rest of what we build. You can also visit our homepage or the about page to learn how we work.