Meridian handbook
For agentsRecipes

Responsive images

How an agent declares image transforms in config/images.yaml, prints asset fields with img(), picture() and figure() so every image has srcset, sizes, dimensions, alt text and its credit, seeds real photographs, and reads the image report from render.

Every image a template prints goes through a named transform. You declare the transforms once; the engine writes srcset, sizes, width, height, alt and the loading attributes, and generates each size the first time it is requested. Never write an <img> for a library image by hand.

Declare the transforms

# config/images.yaml
breakpoints:
  md: 48rem
  lg: 64rem

transforms:
  card:
    aspect: "3:2"
    widths: { min: 320, max: 1200 }
    sizes: "lg:33vw md:50vw 100vw"
  hero:
    aspect: "16:9"
    widths: { min: 640, max: 2560 }
    sizes: "100vw"

sizes names breakpoints from the same file. A template asking for a transform the file does not declare fails check. engine-dmo media transforms prints each transform’s width ladder and the formats this build encodes; the -vips image encodes AVIF and WebP, the standard build JPEG and PNG. A transform with no formats: asks for AVIF and WebP, so on the standard build check prints note: the image transforms name avif and webp, which this build cannot encode. The note is harmless: that build serves JPEG and the -vips image serves all three. formats: [jpeg] on every transform silences it, but also stops the -vips image serving the smaller formats, so leave it unless the person asks.

A transform takes widths: { min, max } for a ladder the engine fills in, or width: for one fixed size (an Open Graph image at 1200 pixels, say); Images has both.

{# the page's main image: eager, high priority, preloaded, with caption and credit #}
{{ walk.photo.figure("hero", priority=true) }}

{# a card image beside a linked title: decorative here, credit printed #}
{% if walk.photo %}
  {{ walk.photo.img("card", alt="") }}
  {% if walk.photo.credit %}<small>{{ walk.photo.credit }}</small>{% endif %}
{% endif %}
MethodPrints
img(transform, ...)One <img> with srcset, sizes, width, height, alt, loading="lazy", decoding="async"
picture(transform, ...)A <picture> with a <source> per format, then the <img>
figure(transform, ...)The picture in a <figure> with the caption and credit in <figcaption>
url(transform, width=, absolute=)One derivative’s address, for a CSS background or a meta tag

Options: priority=true (once per page, for the main image), sizes=, loading=, alt= (alt="" for a decorative use), placeholder=, and any HTML attribute (class="card__img"). An empty asset field prints nothing, so {% if %} is needed only around your own markup.

Rules check enforces: the transform must exist; only one priority=true per page; src, srcset, width, height and style belong to the helper. It warns when an image is printed with img() or picture() and its credit appears nowhere in the template: use figure(), or print .credit.

Seed real photographs

Put each photograph and a YAML file of the same name under seed/assets/:

# seed/assets/towpath.yaml   (beside seed/assets/towpath.jpg)
title: Wildflowers beside the towpath
alt: { en: Wildflowers in bloom along a grassy path }
credit: Photograph by A. Walker
# seed/documents/walk/cherwell-towpath.yaml
locales:
  en:
    title: Cherwell towpath
    slug: cherwell-towpath
    summary: A flat riverside walk along the canal towpath from the town centre.
    difficulty: easy
    photo: { asset: towpath }
publish: [en]

Write alt text that says what the image shows, in every locale the site runs. Use only photographs the person has the right to use, and never a generated image presented as a real place. seed apply runs every file through the same pipeline as an upload and strips location metadata.

Verify

engine-dmo render /walks/cherwell-towpath --json

The images array lists each image printed: its transform, sizes, the width ladder, the formats and every derivative’s address and size. Check that the main image has priority: true and the rest do not, and that a11y is empty. engine-dmo a11y audit fails a page with an <img> that has no alt. See Images for art direction and focal points.