HTML images just got better, with the subtlest change to your codebase
I know what you’re thinking.
Scott, I landed on Medium on a small but not mobile-small display and the download chunk related to images in my home feed was like 7% smaller.
First of all, thank you for noticing.
Second, what if I told you that you too can improve your total asset download size by adding just four characters to your code base?
Sounds too good to be true, right? Well, I’m here to tell you, it’s true… after I explain how we got here.
The history of images on the web
As a web developer, you’ve probably spent a lot of time thinking about and fighting with images.
According to the HTTP Archive, even in today’s modern landscape of “JavaScript everywhere” the majority of most website’s transfer size is images. We’ve moved away from the “hero image on every page” ethos, but still we’re an image-heavy internet.
When I was getting my start in web development in 2010ish, we were still adapting to a “mobile-friendly” world. The majority of the web was built on WordPress.
We didn’t have a way back then to dynamically load an optimized image. 3G connections were the primary speed in the US for viewing the internet, so the practice was images “need to work on mobile” regardless of the user’s device.
For those of us writing Angular and Ember (because React wasn’t a thing yet), you had to legitimately render the correct src based on context you could get from JavaScript.
{{#if browserSmal}}
<img src=”image-high-res.jpg” />
{{else}}
<img src=”image-med-res.jpg” />
{{/if}}This is back when we wrote handlebars for our markup and the web was filling up with SPAs with no server-side-rendering.
We did it this way because HTML lacked a way for specifying how we wanted to optimize images.
Then in 2012, RICG introduced 2 new specs for image optimization, with 2 different use cases: <picture> elements and srcset.
Picture Elements
I love the <picture> element. It gives us a lot of control on how we want to display an image based on the information we know with a smart fallback if compatibility fails.
<picture>
<source srcset=”logo-dark.png” media=”(prefers-color-scheme: dark)” />
<source srcset=”logo-light.png” media=”(prefers-color-scheme: light)” />
<img src=”logo-light.png” alt=”Product logo” />
</picture>
In this example, we can specify which image to display if the user prefers dark mode or light mode. If their browser doesn’t support that check, we specify our default.
We can add logic for screen dimensions, file types (i.e. support for avif or webp), and more. This can be really useful when we want to display different images based on this information.
For example, if we want a landscape banner image on desktop but on mobile, we want a narrow portrait. This isn’t “the same image at different resolutions” but “different images based on our layout.”
<picture> is great when we want a lot of control and have opinions on an image.
Srcset
While the <picture> element was a prescriptive syntax (lets us tell the browser what to do), srcset and is descriptive. We provide the browser with lots of different dimensions of an identical image (ideally) and it decides when to render them.
This new element/attribute is for providing more control, versus making images faster (it can, but most devs find using it that way to be overkill given you have to define every breakpoint).
As web developers, we get no say in what the browser decides here. The algorithms used are intentionally opaque as part of the spec:
In an implementation-defined manner, choose one image source from sourceSet. Let selectedSource be this choice.
— HTML Standards, 4.8.4.3.7 Selecting an image source
Let’s take a quick aside and talk about why the browser owns this implementation because I think it’s important and interesting.
First of all, as JavaScript developers, we don’t have any meaningful tooling around connection speed. A browser might have that level of clarity (it varies from browser to browser and OS to OS). So already, we can’t necessarily make the right call for which image to use based on that speed.
But even if we could we’d be making a vague judgement on what we prefer and not what the user prefers (and not all 3G connections are the same). Then there are additional things the browser knows, like which images have already been cached. If we have the 2x version of an image cached, but our page’s srcset requests the 1x version based on this new layout, which is faster to load? The cached 2x or the smaller 1x that requires a new request?
So the spec says “let the browser handle this.” It has a lot more context than we do and it would be really annoying to write out crazy conditions like srcset=”foo.jpg 480w unless cache is available?, foo.jpg 720w unless bigger cache available?”
So we provide the sizes we ideally want and the browser takes care of selecting which one to render based on context. Great. Let the computer do computer things.
What’s the problem then? Have we handled every optimization case possible?
Not really…
Sizes
In addition to srcset we also got the size attribute in 2012ish. It let us define our expected dimension for given images.
When I first read a blog post about this, it made no sense to me. “Isn’t that what srcset does??”
Again, I’m going to take a step back and look at the problem. When our browser receives HTML from our server, it looks at the img tags and starts fetching those images before the layout is rendered. That’s a really important detail. That means there is no reasonable way that the browser can know how much space an image will take up until after it’s been fetched.
It begins downloading assets, then it paints, renders, and inserts those assets. If it waited to download until after rendering, you’d get empty images and risk layout shift.
So, to help the browser make an even more informed decision, you can specify sizes on your images: sizes=”(width <= 600px) 480px, 800px”
Conceptually, it’s so simple. It makes sense. But when you start getting into practical usages (where you are trying to optimize for lots of scenarios) you start writing these out-of-control syntaxes. Mat Marquis, who helped write the spec for srcset and sizes puts it best:
Describing the sizes of a flexible image will require far too much calculation across breakpoints. (min-width: 1340px) 257px, (min-width: 1040px) calc(24.64vw — 68px), (min-width: 360px) calc(28.64vw — 17px), 80px is an example from a relatively simple layout, and there’s no way anyone could be expected to write this. I mean, how — from, what, resizing your browser and squinting? Guessing? sizes is one of the few markup patterns that all but require the use of tooling, which the furthest possibly cry from the web’s “open any text editor and you can build a website” ethos
— Mat Marquis, from “The End of responsive images”
And that’s just for images we know about? What about user avatars? Blog post images? Random assets we have in sidebars?
Just go to the Medium home feed and see how many images load there.
Almost 70 images, and we have a relatively light image-to-text ratio compared to most websites. (We’re a reading and writing company, after all.)
How can we help the browser make the best decision for what image to load? Well, the solution for that has partially been around for a while: loading=”lazy”
Most images on your website aren’t necessary for first paint. Many of them aren’t even in the viewport at first. For those, we can skip the “fetch asset before rendering” step by saying “this image should only be loaded when we’re confident the user will need it.”
For lazy loaded images, the browser should already have the information it needs for the layout. It should know the optimal version of the image it needs, right?
Well, until a few weeks ago, we had no way of communicating that. Before, we had to write endless breakpoint rules with endless image variants.
But on March 24th, 2026, we got support in WebKit (a similar one to Gecko which powers Firefox) for adding support of “auto” to sizes on lazy-loaded images.
Auto is our motto
Essentially, this says to the browser: “For this image, only load it when we need it and based on the layout, connectivity, dimensions, etc., load the most optimized version.”
We can once again let the browser handle all the heavy lifting of “load the most optimized” image and we actually get to write less code than before.
The best part? The existing sizes spec tells it to ignore any rule it can’t parse. So you can add “auto” to the beginning of all your lazy loaded images, and if the user has a browser that doesn’t yet support it, the rule will just be ignored.
This is a rule that is so performant and backwards compatible that WordPress (remember them?) already updated every instance of lazy loaded images in their codebase to support this.
So if you want an easy-to-implement win that has zero risk of breaking your website, slap an “auto” onto your sizes attributes for lazy loaded images.
That’s how I improved our image download sizes with just four characters. Okay, technically it was 6 characters since I had to add a comma and a space… but still!
How we improved image download sizes on Medium with just four characters was originally published in Medium Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.
Source: medium.engineering
