How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

How-to Images

Hand-write a picture tag when automatic art direction is not enough

The automatic sources choose by viewport width. When the crop must change per breakpoint, build the picture element from resized paths, take each source's type from the converted image, and use getPath(), never getHtml(), inside it.

Context

Context. The generated picture element offers the same crop at several sizes. Two cases need your own markup: art direction, where a phone gets a square crop and a desktop a wide one, and container-relative sizing, where the display size depends on the column, not the screen. Build it from resized paths, and take each source's type from the converted image, so it stays correct when the original is already WebP or an SVG.

Steps

  1. Produce one resized image per crop.
  2. For each, emit a <source> with a media query, the WebP path and the WebP mime type from the converted image.
  3. End with an <img> using the widest original-format path, with width, height and alt.
  4. Use getPath() throughout: with picture tags on, getHtml() would nest a picture inside yours.

Template code

Template code
{$wide   = $image->getResized(1600, 600, 'fit')}
{$square = $image->getResized(600, 600, 'fit')}
<picture>
  <source media="(max-width: 600px)" srcset="{$square->toOptimizedFormat()->getPath()}" type="{$square->toOptimizedFormat()->getMimeType()}">
  <source srcset="{$wide->toOptimizedFormat()->getPath()}" type="{$wide->toOptimizedFormat()->getMimeType()}">
  <img src="{$wide->getPath()}" alt="{$image->getName()|escape}" width="1600" height="600" loading="lazy">
</picture>
{% set wide   = image.getResized(1600, 600, 'fit') %}
{% set square = image.getResized(600, 600, 'fit') %}
<picture>
  <source media="(max-width: 600px)" srcset="{{ square.toOptimizedFormat().path }}" type="{{ square.toOptimizedFormat().mimeType }}">
  <source srcset="{{ wide.toOptimizedFormat().path }}" type="{{ wide.toOptimizedFormat().mimeType }}">
  <img src="{{ wide.path }}" alt="{{ image.name|e }}" width="1600" height="600" loading="lazy">
</picture>

Why this and not that

Not offeredSizes: it gives several sizes of one crop, which is the right tool when the crop does not change. Not a hardcoded type="image/webp": an SVG is never converted and a WebP original is not reconverted, so the type must come from the object. Not the image's name as the alt text without escaping: nothing in getHtml() or your own markup escapes it.

Go deeper

Was this page helpful?

Your answer goes to the docs team.