Combining srcset, sizes, and art direction correctly
A single img tag with a fixed width barely covers any modern layout. Here's how to generate srcset and sizes correctly in a Hyvä theme, when a picture element is genuinely needed for art direction, and how both interact with Magento's image resize cache.
Table of Contents
- 1. Why a single img tag doesn't cover responsive layouts
- 2. srcset and sizes: the mechanics in a Hyvä context
- 3. Where Hyvä already generates srcset, and where it doesn't
- 4. Custom view model logic for multiple image sizes
- 5. The picture element for real art direction
- 6. How this interacts with Magento's image resize cache
- 7. On-the-fly generation versus prebuilt generation via cron
- 8. Measuring performance: the effect on LCP and bandwidth
- 9. A practical checklist for rolling this out
- 10. Summary
- 11. FAQ
1. Why a single img tag doesn't cover responsive layouts
An img tag with a single, fixed image source delivers either an unnecessarily large image on a big desktop monitor or an unnecessarily small one on mobile. Either way costs bandwidth or sharpness, and either way measurably affects Largest Contentful Paint, especially for product images, which are often the largest visible element above the fold.
The problem gets worse with differing pixel densities: a screen with double pixel density needs double the resolution at the same displayed size to look sharp. A single image can never resolve that tradeoff between file size and sharpness, which is why srcset and sizes are mandatory, not optional, for any serious Hyvä frontend.
2. srcset and sizes: the mechanics in a Hyvä context
The srcset attribute lists several image variants along with their actual pixel width, marked with the w descriptor. The sizes attribute, on the other hand, describes how wide the image is actually displayed in the layout at a given viewport width. Only the combination of both lets the browser calculate which variant it actually loads, and it does so before the CSS layout is finalized, which is why the sizes value has to match the actual layout as precisely as possible.
In a Hyvä theme built on a Tailwind-based grid, the sizes value follows directly from the breakpoint classes in use. If a product tile shows a quarter of the width on large viewports and full width on small ones, that exact logic has to be expressed as the sizes attribute, otherwise the browser loads a mismatched variant despite a correct srcset.
<img
src="https://shop.example.com/media/catalog/product/cache/.../product-800.jpg"
srcset="
.../product-400.jpg 400w,
.../product-800.jpg 800w,
.../product-1200.jpg 1200w
"
sizes="(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw"
loading="lazy"
alt="Product name"
>
3. Where Hyvä already generates srcset, and where it doesn't
Hyvä's core templates for product lists and category pages already generate a srcset for the predefined image types such as category_page_grid or product_page_image_large, based on the widths configured in view.xml. For many standard cases that's enough, because the widths defined there cover typical grid layouts.
As soon as a custom layout deviates from Magento's default image types, say a three-column product tile with its own breakpoints or a hero banner with an unusual aspect ratio, the default configuration usually falls short. In that case additional image widths have to be added to view.xml and the srcset generation has to be adjusted in the corresponding view model or template.
4. Custom view model logic for multiple image sizes
For custom image widths, a dedicated view model that uses the image factory service to generate several sizes for the same image source and returns a ready-made srcset attribute to the template is the cleanest approach. That keeps the image-size generation logic centralized and testable, instead of duplicated across several templates.
What matters is that every additional width creates its own combination of image type and target size in the resize cache. Overly granular steps, say five or six widths for the same image, do increase the theoretical fit, but also substantially increase the number of cache files that need generating and storing, without a perceptible quality gain past a certain point.
<?php
declare(strict_types=1);
namespace Mironsoft\Core\ViewModel;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Model\Product;
use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* Builds a srcset attribute with several image widths for a product image.
*/
class ResponsiveImage implements ArgumentInterface
{
private const WIDTHS = [400, 800, 1200];
public function __construct(private readonly ImageHelper $imageHelper)
{
}
/**
* Builds the srcset string for a product and an image type.
*
* @param Product $product
* @param string $imageType
* @return string
*/
public function getSrcSet(Product $product, string $imageType): string
{
$parts = [];
foreach (self::WIDTHS as $width) {
$url = $this->imageHelper
->init($product, $imageType)
->constrainOnly(true)
->keepAspectRatio(true)
->resize($width)
->getUrl();
$parts[] = sprintf('%s %dw', $url, $width);
}
return implode(', ', $parts);
}
}
5. The picture element for real art direction
srcset and sizes only solve the resolution problem: the same image composition just gets delivered at different sizes. If the crop itself needs to differ per viewport, say a wide landscape shot on desktop and a tighter cropped portrait on mobile, srcset isn't enough. That requires the picture element with several source tags and media conditions.
Each source line defines its own image source for a specific viewport condition, and the browser picks the first matching condition from top to bottom. The trailing img tag acts as a fallback for browsers without picture support and should therefore always hold a sensible default variant, usually the mobile version as the most conservative choice.
<picture>
<source
media="(min-width: 1024px)"
srcset=".../hero-wide-1600.jpg 1600w, .../hero-wide-2400.jpg 2400w"
sizes="100vw"
>
<source
media="(min-width: 640px)"
srcset=".../hero-square-1000.jpg 1000w"
sizes="100vw"
>
<img
src=".../hero-portrait-800.jpg"
loading="lazy"
alt="Campaign visual"
>
</picture>
6. How this interacts with Magento's image resize cache
Every requested combination of original image, image type, and target width creates a new file under pub/media/catalog/product/cache on first request. With consistent use of srcset across several widths, plus picture-based art direction with custom image sources on top, the number of these cache files multiplies quickly, especially for large catalogs with thousands of products.
That multiplication isn't inherently a problem as long as it's planned deliberately. It becomes critical when widths get defined independently and uncoordinated across several templates, producing far more variants for the same image than the layout actually uses. A centralized constant definition for allowed widths, as in the view model example above, prevents that uncontrolled multiplication.
7. On-the-fly generation versus prebuilt generation via cron
By default, Magento generates missing image variants on the fly on first access, which noticeably slows down the very first page load for a given image size. For shops with a predictable catalog, say after a large import, prebuilding via the catalog:images:resize command is worth it instead, generating every configured image type and width in advance.
For newly created products that go live between two cron runs, on-the-fly generation stays active as a fallback either way. In practice, a combination works best: run catalog:images:resize after every larger import or after every view.xml change, but don't disable on-the-fly generation as a safety net for edge cases.
bin/magento catalog:images:resize
bin/cache-clean
8. Measuring performance: the effect on LCP and bandwidth
The measurable effect of correctly configured srcset values shows up most clearly in Largest Contentful Paint, especially on mobile devices with limited bandwidth. A product image delivered at full desktop resolution to a smartphone without srcset can easily push the LCP value back by several hundred milliseconds, purely from the extra transfer time.
For measurement, comparing the network tab data in Chrome DevTools before and after introducing srcset works best, specifically the actual transferred file size on a simulated mobile device. That concrete number is far easier to communicate than an abstract Lighthouse score and shows the effect directly in transferred kilobytes.
9. A practical checklist for rolling this out
The cleanest way in is a fixed sequence: document the actual layout widths per breakpoint first, derive the matching sizes values from that, then define the required image widths in view.xml or the view model, and only at the end check whether any area genuinely needs real art direction with a picture element.
The table below compares the techniques covered here by effort and impact, so teams can focus on the change with the best ratio first, instead of rolling out every technique at once and losing track of the cache implications.
| Technique | Solves which problem | Cache impact | Effort |
|---|---|---|---|
| srcset with w descriptor | Wrong resolution per viewport | One file per defined width | Low |
| sizes attribute | Wrong browser display-width calculation | No additional cache file | Low |
| picture with multiple source | Wrong crop per viewport | Multiple files per breakpoint | Medium |
| Prebuilt resize via cron | Slow first page load | Reduces on-the-fly generation | Medium, one-time setup |
| Centralized width constant in view model | Uncoordinated width multiplication | Deliberately caps cache growth | Low |
Mironsoft
Hyvä theme development and Luma migration
Still running Luma, or a Hyvä theme that just doesn't feel right?
We build Hyvä themes for Magento from scratch or migrate existing Luma shops cleanly, with Tailwind CSS, Alpine.js, and none of the unnecessary JavaScript baggage.
Luma-to-Hyvä Migration
Move an existing shop to Hyvä in a structured way, without losing functionality.
Custom Theme Development
Build a custom Hyvä theme from scratch based on your design.
Performance Optimization
Improve Core Web Vitals and load times in the Hyvä frontend with purpose.
10. Summary
Responsive Images in Hyvä: Key Facts at a Glance
Core rule
srcset solves the resolution problem, picture solves the art direction problem, they cover different jobs.
sizes precision
The sizes attribute must exactly match the real Tailwind grid logic, otherwise the browser loads the wrong variant.
Cache growth
Every additional width multiplies resize cache files, centralized width constants keep it under control.
Prebuilt generation
catalog:images:resize after import or a view.xml change avoids a slow first page load.