Skip to content

How-to guideFor theme developers, site buildersDrupal 11

Images and media

Editors upload one original image; the theme decides every size and crop that visitors actually download. This page covers the pipeline that makes that work: image styles, responsive image styles bound to your theme’s breakpoints, and the templates behind media entities.

Define the derivative sizes your design needs, serve the right one per viewport through a picture element, and override the markup around images and media. Authoring image-processing plugins (toolkits and effects) is out of scope here; that API is documented in the Image toolkits topic.

  • A Drupal 11 site with the Image module (standard installs have it). The responsive image section needs the Responsive Image module: ddev drush pm:install responsive_image.
  • A theme with breakpoints defined in THEME.breakpoints.yml; the first theme tutorial part 3 sets these up.
  • For the media section, the Media module with at least one media type.

An image style is a named recipe of effects (scale, crop, convert) applied to an original to produce a derivative. Create one at Administer → Configuration → Media → Image styles/admin/config/media/image-styles: select Add image style, name it, and add effects. For a card thumbnail, one Scale and crop effect with the target dimensions is typical.

Image styles are configuration entities, so the result of clicking through the UI is a config export you can review and deploy:

web/modules/custom/docs_snippets/config/optional/image.style.theming_howtos_card.yml
# An image style is a config entity. This is what an export looks like
# (drush config:export writes one such file per style).
langcode: en
status: true
dependencies: { }
name: theming_howtos_card
label: 'Card (480×270)'
effects:
# Effects are plugins; each instance is keyed by a UUID.
b9bf2bcb-9a96-4a9b-8911-3aaa5e9b9c43:
uuid: b9bf2bcb-9a96-4a9b-8911-3aaa5e9b9c43
id: image_scale_and_crop
weight: 0
data:
anchor: center-center
width: 480
height: 270

Derivatives are generated lazily. Saving the style creates no files; the first request for a derivative URL generates the file under sites/default/files/styles/STYLE_NAME/ and subsequent requests are served statically. The URL carries an itok token so visitors cannot fill your disk by requesting arbitrary derivative sizes. Editing or deleting a style flushes its existing derivatives.

For a field, no code is needed: under Manage display, set the image field’s format to Image and pick the image style per view mode (a named rendering configuration such as teaser or full).

In code, build an image_style render array. A common place is a preprocess hook that hands the template a ready-to-print variable:

web/modules/custom/docs_snippets/src/Hook/ThemingHowtosHooks.php
/**
* Implements hook_preprocess_HOOK() for node templates.
*
* Exposes image derivatives to Twig as ready-to-print render arrays.
* The derivative file itself is only generated on the first request
* for its URL, not here.
*/
#[Hook('preprocess_node')]
public function preprocessNode(array &$variables): void {
$node = $variables['node'];
if (!$node->hasField('field_image') || $node->get('field_image')->isEmpty()) {
return;
}
$uri = $node->get('field_image')->entity->getFileUri();
// One fixed-size derivative: {{ card_image }} in node.html.twig.
$variables['card_image'] = [
'#theme' => 'image_style',
'#style_name' => 'theming_howtos_card',
'#uri' => $uri,
'#alt' => $node->get('field_image')->alt,
];
// A <picture> element that picks a derivative per theme breakpoint:
// {{ hero_image }}.
$variables['hero_image'] = [
'#theme' => 'responsive_image',
'#responsive_image_style_id' => 'theming_howtos_hero',
'#uri' => $uri,
'#attributes' => ['alt' => $node->get('field_image')->alt],
];
}

Then in node.html.twig: {{ card_image }}. The render element computes the derivative’s dimensions without generating the file, so the img tag gets correct width and height attributes and the page does not shift while images load.

One derivative size is still wrong for someone: a 1200 pixel hero is wasteful on a phone, and a 480 pixel thumbnail is blurry on a wide screen. The Responsive Image module generates picture elements that let the browser pick per viewport, driven by two pieces:

  1. Breakpoints come from your theme’s THEME.breakpoints.yml; the first theme defines three, each available at 1x and 2x:

    web/themes/custom/first_theme/first_theme.breakpoints.yml
    # Breakpoints let Drupal's responsive image styles and other modules react
    # to the same screen sizes your CSS uses. Mobile first: smallest has no
    # media query.
    first_theme.mobile:
    label: Mobile
    mediaQuery: ''
    weight: 0
    multipliers:
    - 1x
    - 2x
    first_theme.tablet:
    label: Tablet
    mediaQuery: 'all and (min-width: 48rem)'
    weight: 1
    multipliers:
    - 1x
    - 2x
    first_theme.desktop:
    label: Desktop
    mediaQuery: 'all and (min-width: 75rem)'
    weight: 2
    multipliers:
    - 1x
    - 2x
  2. A responsive image style maps each breakpoint and multiplier to an image style. Build it at Administer → Configuration → Media → Responsive image styles/admin/config/media/responsive-image-style: pick your theme’s breakpoint group, assign an image style per breakpoint, and set the fallback style used when nothing matches. Exported, it looks like this:

    web/modules/custom/docs_snippets/config/optional/responsive_image.styles.theming_howtos_hero.yml
    # A responsive image style maps each theme breakpoint (and multiplier) to
    # an image style. Build it at /admin/config/media/responsive-image-style;
    # this is the exported result.
    langcode: en
    status: true
    dependencies:
    config:
    - image.style.large
    - image.style.medium
    - image.style.wide
    theme:
    # The breakpoint group comes from first_theme.breakpoints.yml.
    - first_theme
    id: theming_howtos_hero
    label: Hero
    image_style_mappings:
    -
    image_mapping_type: image_style
    image_mapping: medium
    breakpoint_id: first_theme.mobile
    multiplier: 1x
    -
    image_mapping_type: image_style
    image_mapping: large
    breakpoint_id: first_theme.mobile
    multiplier: 2x
    -
    image_mapping_type: image_style
    image_mapping: large
    breakpoint_id: first_theme.tablet
    multiplier: 1x
    -
    image_mapping_type: image_style
    image_mapping: wide
    breakpoint_id: first_theme.desktop
    multiplier: 1x
    breakpoint_group: first_theme
    fallback_image_style: medium

Apply it the same two ways as a plain style: the Responsive image field format under Manage display, or a responsive_image render array (the hero_image variable in the preprocess snippet above).

For fluid-width images, the alternative sizes mapping type puts multiple candidate styles in one srcset with a sizes attribute and lets the browser do all the math; the Responsive Image module ships a single viewport_sizing breakpoint group for exactly that setup.

Media items are content entities: they render through view modes, and each media type’s Manage display decides which fields show. The themable layers, from outside in:

  • media.html.twig wraps every media item. The Media module provides suggestions per view mode, bundle, and source plugin, so media--image.html.twig or media--remote-video--card.html.twig target one type or one type-and-view-mode pair, exactly like node templates (Twig in Drupal covers the workflow).
  • Inside it, {{ content }} renders the configured fields; for an image media type that is an image field, so your image style and responsive image formatters from the previous steps apply here too.
  • From a host entity’s template, a media reference field renders with {{ content.field_media }} like any other field, and the media item’s own templates take over inside it.

The practical recipe for “make embedded images look right site-wide”: create a dedicated view mode for the media type, configure its image field with a responsive image style, and select that view mode wherever the media is referenced (including the media embed dialog in CKEditor).

  1. After defining the config above (or clicking it together in the UI), run ddev drush cache:rebuild (ddev drush cr) and visit an article that has an image.
  2. The page source shows a picture element for the hero with one source per breakpoint, and the img fallback pointing at a /sites/default/files/styles/... URL with an itok query parameter.
  3. The derivative files exist on disk under sites/default/files/styles/ only after the page has been requested once.
  4. Resize the browser window across your breakpoint widths (48rem and 75rem in the first theme) and confirm the loaded image swaps in the network panel.

Derivatives are cached files; if you change a style outside the UI (config import, update hook), flush its old files with ddev drush image:flush STYLE_NAME (or all styles with ddev drush image:flush --all).

Conversion is an effect like any other: add a Convert effect to the style. Core’s own large style converts derivatives this way, so the source file format stops mattering for delivery weight.

Mapping different crops per breakpoint (art direction) and different sizes of the same crop (resolution switching) both work with the per-breakpoint mapping; pure resolution switching is often served better by one sizes mapping. If your image styles only scale, start with sizes.

Was this page helpful?

Drupal is a registered trademark of Dries Buytaert.