Responsive images are about to get a whole lot easier. Using sizes="auto"
for lazy loaded images is supported in Chrome 126+. Apple and Mozilla have voiced their plans to support it.
Shopify has released a polyfill for this feature too! Now is a great time to try sizes="auto"
on your next production project.
Life before sizes=auto
I’ve never enjoyed writing out the sizes
attribute for responsive images. It’s a necessary hassle to build a well-performing site. But there’s so much context you, as a developer, need to keep in your head:
- How will my CSS & breakpoints affect the width of these images?
- If I change a breakpoint or grid layout, will I have to update every image on the site?
- Or even more likely… I flat out forget to add sizes until the very end of a project.
sizes="(min-width: 1440px) 1200px, (min-width: 768px) calc(100vw - 320px), (min-width: 640px) calc(100vw - 64px), calc(100vw - 24px)"
👆 That’s a lot of math.
Back before loading="lazy"
was widely supported, I used the lazysizes library. One of its killer features was automatic sizes
. Just plop in data-sizes="auto"
and it would figure out the rest. However, now that lazy loading is a baseline browser feature, it doesn’t make sense to load up the entire library just for one feature (it doesn’t seem to be actively maintained either).
Well, guess what? Just like loading="lazy"
of old, sizes="auto"
is ready for prime time. It’s already available in Chromium 126+… which was released (checks notes) over a year ago!
But what to do about other browsers?
The Promise of sizes="auto"
Chris Coyier wrote about this back in 2023, and I’ve been patiently hoping it would gain traction. The idea is beautifully simple. If the image is lazily loaded, the browser has time to calculate the sizes
value based on the actual rendered size of the image.
What if we could go from this (manually writing sizes):
<img
alt="A sea turtle swimming gracefully through crystal clear water."
width="2000"
height="1500"
srcset="
turtle-300.jpg 300w,
turtle-600.jpg 600w,
turtle-1200.jpg 1200w,
turtle-2000.jpg 2000w
"
sizes="
(min-width: 2420px) 2000px,
(min-width: 720px) calc(94.76vw - 274px),
(min-width: 520px) calc(100vw - 96px),
calc(100vw - 32px)
"
loading="lazy"
>
Adapted from Chris Coyier’s example
To using sizes="auto"
(width and height are recommended):
<img
alt="A sea turtle swimming gracefully through crystal clear water."
width="2000"
height="1500"
srcset="
turtle-s.jpg 300w,
turtle-m.jpg 600w,
turtle-l.jpg 1200w,
turtle-xl.jpg 2000w
"
sizes="auto"
loading="lazy"
>
That’s it! No math, no media queries, no maintenance headaches.
Browser Support & The Shopify Polyfill
The sizes="auto"
attribute is supported in Chromium 126+ (caniuse). That’s great for Chrome and Edge, but what about everything else?
Apple and Mozilla have both updated their standards position repos, signaling their support for this feature.
With all major browser engines on board, I’ve thought about writing a polyfill for a long while. However, the fine folks at Shopify beat me to it (and probably did a better job than I could). Their autosizes polyfill was released in May 2024, and it’s impressively lightweight. Coming in at roughly 160 lines of code.
How the Polyfill Works
Here’s the clever part, straight from their GitHub repo:
The polyfill uses mutation observers to detect newly added HTMLImageElement elements with loading=lazy and sizes=auto attributes.
It then removes any src and srcset attributes from those images, ensuring that they won’t start loading images.
Once First-Contentful-Paint is fired, which we use as a proxy to indicate that layout was calculated:
- sizes attribute value gets overridden by the image’s getBoundingClientRect pixel size.
- The attributes get written back as src and srcset, so that the image can load. Images that are changed or added after FCP fires get their sizes attribute rewritten without removing their src and srcset.
The polyfill backs off if the browser is too old to support FCP, or if it supports sizes=auto natively. Unfortunately, the latter information is only available through User-Agent sniffing.
Under the hood, it’s using the PerformanceObserver API to detect when First Contentful Paint occurs:
new PerformanceObserver((entries, observer) => {
entries.getEntriesByName('first-contentful-paint').forEach(() => {
didFirstContentfulPaintRun = true;
setTimeout(restoreImageAttributes, 0);
observer.disconnect();
});
}).observe({type: 'paint', buffered: true});
Browser Support
Here’s the current state of browser support for sizes="auto"
:
Native Support:
- Chrome/Edge 126+ (July 2024)
- Opera 112+
- Safari: Not yet supported
- Firefox: Not yet supported
With Polyfill:
- Chrome/Edge: All versions (polyfill detects native support and backs off)
- Opera
- Safari: 14.1+ (supports PerformancePaintTiming API)
- Firefox: 84+ (requires PerformancePaintTiming API)
- Any browser that supports the PerformancePaintTiming API
See MDN: PerformancePaintTiming browser compatibility
Practical Implementation
If you’re using an image transformation service like Imgix, Cloudinary, or AWS serverless transforms, you can create reusable components that make responsive images trivial to implement.
React Example
function ResponsiveImage({ src, width, height, alt = "", className = "" }) {
const sizes = [400, 800, 1200, 1800, 2600];
// Generate srcset for Cloudinary (adjust for your service)
const srcset = sizes
.map(width =>
`https://res.cloudinary.com/your-cloud/image/fetch/w_${width},f_auto,q_auto/${encodeURIComponent(src)} ${width}w`
)
.join(', ');
return (
<img
src={`https://res.cloudinary.com/your-cloud/image/fetch/w_800,f_auto,q_auto/${encodeURIComponent(src)}`}
srcSet={srcset}
sizes="auto"
loading="lazy"
alt={alt}
className={className}
width={width}
height={height}
/>
);
}
// Usage
<ResponsiveImage
src="https://example.com/turtle.jpg"
alt="A sea turtle swimming gracefully through crystal clear water."
width="2000"
height="1500"
/>
Twig Example (Craft CMS)
I write a lot of Twig at work. This method works great for any framework or language.
{# _macros/image.twig #}
{% macro ResponsiveImage(props={}) %}
{% set src = props.src %}
{% set width = props.width ?? null}
{% set height = props.height ?? null}
{% set alt = props.alt ?? '' %}
{% set class = props.class ?? null %}
{% set sizes = [400, 800, 1200, 1800, 2600] %}
{% set srcset = sizes | map(width => "https://res.cloudinary.com/your-cloud/image/fetch/w_#{width},f_auto,q_auto/#{src | url_encode} #{width}w") %}
<img
src="https://res.cloudinary.com/your-cloud/image/fetch/w_800,f_auto,q_auto/{{ src | url_encode }}"
srcset="{{ srcset | join(', ') }}"
sizes="auto"
loading="lazy"
alt="{{ alt }}"
width="{{ width }}"
height="{{ height }}"
{% if class %}class="{{ class }}"{% endif %}
>
{% endmacro %}
{# In your template #}
{% from '_macros/image.twig' import ResponsiveImage %}
{{ ResponsiveImage({
src: 'https://example.com/turtle.jpg',
alt: 'A sea turtle swimming gracefully through crystal clear water.',
class: 'hero-image',
width: 2000,
height: 1500,
}) }}
The Verdict? Ready for Prime Time!
After digging into the browser support and standards positions, I think that sizes="auto"
is ready for production use.
What do you think? Are you already using sizes="auto"
, or do you have reservations about adopting it? I’d love to hear about your experiences - find me on LinkedIn.