You enable Drupal's Responsive Image module, open its settings page, and find two styles called Narrow and Wide, a list of breakpoints and a field asking for an image style per breakpoint. The official documentation stops about there. This guide picks up where it ends, with the setup we use on five live sites.
The problem responsive images solve
On a phone, a card in a grid is about 340 pixels wide. Without responsive images, Drupal sends the same 900-pixel file it sends to a desktop, and the browser shrinks it. The page looks fine, but the phone has downloaded two or three times more than it needed, and that file is often what decides your Largest Contentful Paint (LCP), the speed metric Google cares most about.
CSS alone (max-width: 100%) makes an image fit. It doesn't make it smaller to download. For that the browser needs a choice of files and a hint about how big the image will be on screen. Drupal's Responsive Image module produces exactly that: an <img> with a srcset (the files) and a sizes attribute (the hint).
When we moved our food sites to this setup, mobile PageSpeed went from 74 to 100. Images weren't the only change, but they were the largest one.
How the pieces fit
Four pieces work together. The order matters, because each one uses the one before it:
- Image styles make the actual files: the same picture cropped to one ratio and saved at a few widths, for example 360 and 720 pixels, as WebP.
- A responsive image style groups those image styles and says how to offer them to the browser.
- A view mode for Media (we'll call ours Card) shows an image using that responsive style.
- Your content displays and Views use that view mode wherever a card image appears.
Most confusion comes from step 3. Since Drupal moved images into Media, you rarely apply a responsive style to a content field directly. You apply it to a media view mode, and everything else points to that view mode.
Breakpoints in two minutes
A breakpoint is a screen width where your layout changes: for example, where one column becomes two, or the menu turns into a button. Your theme defines its breakpoints in a file called yourtheme.breakpoints.yml, grouped by purpose. Olivero, for instance, changes its layout at 500, 700, 1000 and 1200 pixels, among others.
The Responsive Image module asks you for a breakpoint group before anything else, which makes it look as if you need to master breakpoints first. You don't. With the setup in this guide, you choose the group called Responsive image and never map a style to a breakpoint. What you do need is to know where your layout changes, because you'll describe those widths in the sizes attribute (step 3).
To find them, open the theme's breakpoints file, or simply make your browser window narrower and note the widths where your cards go from three per row to two, and from two to one.
Choosing and defining your own breakpoints, and using them to show a different crop on each screen, is a topic of its own. We cover it in a follow-up article.
Decide first: sizes or art direction
The module offers two ways of working, and choosing the right one saves most of the trouble.
- Several image styles plus
sizes. The image keeps the same crop at every size; only its resolution changes. The browser picks the file. This is what you want for cards, teasers, article images and most heroes. - One image style per breakpoint (art direction). The image changes its crop: a wide panorama on desktop, a square on a phone. Drupal outputs a
<picture>element and you map a style to each breakpoint.
Start with the first. Use art direction only when the crop really has to change, and leave it for when the simple setup is working.
Step 1: create the image styles
Go to Configuration β Media β Image styles and add one style per file size you want to offer. For a card that is never shown wider than 360 pixels, two are enough:
- Card 360: effect Scale and crop, 360 Γ 240; effect Convert, WebP.
- Card 720: effect Scale and crop, 720 Γ 480; effect Convert, WebP.
Three rules make this work:
- One ratio per set. Both styles are 3:2. If the widths differ but the ratio doesn't, the browser can swap files without the layout jumping.
- The largest display width, at 1x and 2x. 360 covers normal screens; 720 covers phones and laptops with high-density ("retina") screens, which show two pixels for each CSS pixel.
- WebP. It's usually 25β35% smaller than JPEG at the same quality, and every current browser supports it.
Images that fill the whole width of the page, such as a hero, need more steps: for example 640, 960, 1280 and 1920. There's no need for twenty sizes; a handful of well-chosen widths gets you nearly all of the benefit.
Step 2: create the responsive image style
Go to Configuration β Media β Responsive image styles β Add responsive image style and fill it in:
- Label: Card.
- Breakpoint group: Responsive image. This group has a single breakpoint and is meant for exactly this setup.
- Under that breakpoint, choose Select multiple image styles and use the sizes attribute.
- Tick Card 360 and Card 720.
- Sizes: leave it for a moment; it's step 3.
- Fallback image style: Card 360. Very old browsers get this one, and Drupal also takes the
widthandheightattributes from it, so pick the size the image is shown at on a normal screen.
Step 3: write the sizes attribute
sizes is the part almost everyone gets wrong, because it seems to ask about screens. It doesn't. It tells the browser how wide the image will be on the page at each screen width, before the CSS has loaded. With that, and with the screen's pixel density, the browser picks the smallest file that will look sharp.
Measure your card at three widths (browser DevTools, or just a ruler on a screenshot):
| Screen width | Layout | Card image width |
|---|---|---|
| 1200 px and wider | 3β4 cards per row | 360 px at most |
| 768β1199 px | 2 cards per row | about half the screen |
| below 768 px | 1 card per row | the whole screen |
Then write it down in that order, from the widest screen to the narrowest. The first condition that matches wins, and the last value has no condition:
(min-width: 1200px) 360px, (min-width: 768px) 50vw, 100vwvw means a percentage of the screen width: 50vw is half, 100vw is all of it. Slightly overestimating is harmless; underestimating gives blurry images. Paste the line into the Sizes field of the responsive image style and save.
To check what the browser chose, open DevTools, select the image and look at currentSrc in the Properties panel, or simply hover the <img> in the Elements panel. On a normal laptop you should see the 360 file; on a phone or a high-density screen, the 720 one.
If you change your grid later (four cards per row instead of three, say), come back and update sizes. It's the one setting that has to follow your layout.
Step 4: create a media view mode
This is the step the official documentation leaves out, and it's where the responsive style actually gets used.
- Go to Structure β Display modes β View modes β Add view mode, choose Media and call it Card.
- Go to Structure β Media types β Image β Manage display. At the bottom, under Custom display settings, tick Card and save.
- Open the new Card tab. Set the image field's format to Responsive image, then click the cog and choose the Cardresponsive style.
- In the same settings, set Link image to: Nothing. The link to the article will come from where the image is used, not from the media item.
- Hide the field label and any other fields (name, author). Save.
Now, anywhere in the site, "show this image as a Card" means "use the Card responsive style". If you later change the styles or sizes, every card on the site follows.
Step 5: use it
In content displays
In Structure β Content types β Article β Manage display, open the display you want (Teaser, for example). Set the media field's format to Rendered entity and its view mode to Card. Every teaser now uses responsive images.
The full article display usually needs a bigger image. Create a second set the same way (an Article view mode with styles of, say, 800 and 1600 pixels) rather than stretching the Card one.
In Views
Add the media field and choose Rendered entity β Card. That's all, until you want the image to link to the article, which is where most sites go wrong.
The tempting way is Rewrite results: write your own HTML with a link around {{ field_image }}. Don't. Views passes rewritten output through a security filter that cleans up the HTML, and in the process it can mangle the image's attributes. We saw sizes arrive as " 768px) 50vw, 100vw": the browser no longer understands it and always downloads the largest file.
Link the image without rewriting it:
- Add the field Content: Link to Content. Tick Exclude from display and Output the URL as text. Drag it abovethe image field, so its value is available to it.
- Edit the image field. Under Rewrite results, leave "Override the output of this field" unticked. Tick Output this field as a custom link and enter
{{ view_node }}as the link path. - Save the view.
The image is now a link to the article and its srcset and sizes stay intact.
In body text
Images that editors place in the text with CKEditor 5 are a different case. If they're inserted as Media, they use the view mode chosen in the editor's media settings, so you can point that to a responsive one. Images inserted as plain files get no srcset; for those, upload them at a sensible size (no wider than the text column at 2x) and in WebP.
Loading and LCP
For every image below the first screen, loading="lazy" is right: the browser fetches it only when the reader scrolls near it. Drupal adds it by default to image fields, and the responsive image formatter has an Image loading setting where you can check it.
The exception is the main image at the top of the page, usually your LCP element. It should load at once:
- Set it to eager in its formatter's Image loading setting.
- If it lives in body text or a custom block, be aware that core's Lazy load images text filter adds
loading="lazy"to every image it finds. Writeloading="eager"on the hero yourself; the filter leaves images that already have the attribute alone. - Add
fetchpriority="high"to that one image so the browser requests it before everything else. Core has no setting for it yet; in a text field you write it into the HTML, and for fields there are contributed formatters.
We gained more LCP from fixing one lazy-loaded hero than from any other single change.
Should you create your own styles?
Yes. The Narrow and Wide styles that come with core are examples built around Olivero's widths and ratios, not your grid. They show how the module works; they're not a setup to rely on.
Name your styles after their use (Card, Hero, Article), not their size. When the design changes and cards grow to 400 pixels, you edit Card 360 and Card 720 in one place, and nothing that uses "Card" has to change.
Pitfalls we ran into
- Views rewrites. Covered above: never wrap a responsive image in rewritten HTML.
- Width and height from the fallback. Drupal writes the fallback style's dimensions on the
<img>, which reserves the right space while the page loads but can render the image at the wrong size in some layouts. Pick the fallback that matches the normal display size. There are open core issues about this (#3516726, #3523451). - Hosting optimisers. Some hosts rewrite images and CSS on the fly (mod_pagespeed and similar). They recompress files Drupal has already optimised, rename them, and keep their own cache. Turn them off and let Drupal do the work.
- Trusting PageSpeed blindly. PageSpeed may report that a 720-pixel file is "larger than displayed" on a 340-pixel card. On a phone with a 2x screen, 720 is exactly what makes it sharp.
Checklist
- Image styles: one ratio, the largest display width at 1x and 2x, WebP.
- Responsive image style: group Responsive image, sizes option, both styles ticked, sensible fallback.
sizeswritten from the real layout, widest screen first.- Media view mode with the Responsive image formatter, linked to nothing.
- Content displays and Views use Rendered entity β that view mode.
- Image links in Views via a hidden Link to Content field, no rewrite.
- Lazy loading below the fold; the hero eager, with
fetchpriority="high". - Check
currentSrcin DevTools, then run PageSpeed before and after.
Where to learn more
- Responsive images, the official page on drupal.org.
- Working with breakpoints in Drupal, on breakpoint groups and the theme's breakpoints file.
- Responsive image best practices for Drupal, Four Kitchens, with more on choosing widths.
- Mastering Responsive Images: A Drupal Guide, The DropTimes, a summary of Mario HernΓ‘ndez's series.
- Responsive image styles in Drupal, a video course on Drupalize.Me.