PHPackages                             massif/statamic-responsive-images - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Image &amp; Media](/categories/media)
4. /
5. massif/statamic-responsive-images

ActiveStatamic-addon[Image &amp; Media](/categories/media)

massif/statamic-responsive-images
=================================

Production-ready responsive image Antlers tag for Statamic 6.

v1.0.0(1mo ago)0103↓50%[1 issues](https://github.com/massif-web/statamic-responsive-images/issues)MITPHPPHP ^8.2

Since Apr 24Pushed 1mo agoCompare

[ Source](https://github.com/massif-web/statamic-responsive-images)[ Packagist](https://packagist.org/packages/massif/statamic-responsive-images)[ RSS](/packages/massif-statamic-responsive-images/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (3)Versions (2)Used By (0)

Responsive Images
=================

[](#responsive-images)

A production-ready Statamic 6 addon that renders responsive `` elements from a single Antlers tag. Format negotiation (AVIF → WebP → fallback) is handled by the browser, intrinsic dimensions are always set to prevent CLS, and a tiny inline LQIP is rendered behind the image while it loads.

Srcset generation follows the [next/image](https://nextjs.org/docs/app/api-reference/components/image) model: a pool of candidate widths built from `device_sizes` ∪ `image_sizes`, capped at the source image's intrinsic width.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Statamic 6
- Statamic Glide enabled (default)

Installation
------------

[](#installation)

```
composer require massif/statamic-responsive-images
```

The addon auto-registers via its service provider. Publish the config if you want to tweak defaults:

```
php artisan vendor:publish --tag=responsive-images-config
```

Quickstart
----------

[](#quickstart)

```
{{ responsive_image src="hero.jpg" alt="A sunset over the coast" }}
```

Output (simplified):

```

```

With an asset field:

```
{{ responsive_image :src="hero" ratio="16/9" sizes="(min-width: 1024px) 50vw, 100vw" }}
```

Parameters
----------

[](#parameters)

ParamTypeDescription`src`string|Asset**Required.** URL, `assets::id`, or Asset instance. Empty values render nothing.`alt`stringAlt text. Falls back to the asset's `alt` field. Missing alt is logged as a warning.`sizes`string`sizes` attribute. Defaults to `default_sizes` from config.`widths`array|csvOverride the srcset pool. E.g. `widths="400,800,1200"`.`ratio`stringForce an aspect ratio. Accepts `16/9` or `16:9`. Drives crop height on every srcset entry.`width`intExplicit intrinsic width.`height`intExplicit intrinsic height.`fit`stringGlide fit mode. Defaults to `glide.default_fit` (`crop_focal`) when a ratio is set. Without a ratio, URLs use `fit=contain` so Glide scales proportionally.`class`stringClass for the outermost rendered element. Lands on the wrapper when `figure` or `ratio_wrapper` is used, otherwise on the ``.`img_class`stringClass applied directly to the ``. Merges with `class` when there's no wrapper.`loading`string`lazy` (default), `eager`.`decoding`string`async` (default), `sync`, `auto`.`fetchpriority`string`auto` (default), `high`, `low`.`preload`boolPush a `` for the top enabled format onto the Antlers `head` stack. Works out of the box — Statamic's default head partial already renders that stack. See [Preload](#preload) below.`quality`intOverride the quality for all formats on this render. Defaults to per-format quality from config.`formats`csv|arrayLimit which formats are emitted. E.g. `formats="webp,fallback"`. Valid entries: `avif`, `webp`, `fallback`.`blur`intGlide blur passthrough.`brightness`intGlide brightness passthrough (-100..100).`contrast`intGlide contrast passthrough (-100..100).`sharpen`intGlide sharpen passthrough (0..100).`gamma`floatGlide gamma passthrough.`pixelate`intGlide pixelate passthrough.`filter`stringGlide filter passthrough (e.g. `sepia`, `greyscale`).`flip`stringGlide flip passthrough (`h`, `v`, `both`).`orient`int|stringGlide orient passthrough (exif value or `0`/`90`/`180`/`270`).`bg`stringGlide background colour passthrough (hex, rgb, rgba).`placeholder`boolSet `false` to disable the inline LQIP for this tag.`figure`boolWrap output in ``.`caption`string|boolCaption text (only rendered in figure mode). When omitted, the figure auto-captions from the resolved `alt` text. Pass `caption="false"` to disable the auto-caption.`ratio_wrapper`boolWrap output in a ``.`sources`arrayArt-direction sources. See below.### Classes

[](#classes)

`class` targets the outermost rendered element. When you wrap the output in a `` or a ratio ``, the class lands on that wrapper; otherwise it lands on the ``. If you also pass `img_class` in the wrapperless case, both are merged on the ``.

```
{{# No wrapper → class goes on the . #}}
{{ responsive_image :src="hero" class="rounded shadow" }}

{{# Wrapper present → class goes on the , img_class on the . #}}
{{ responsive_image :src="hero" figure="true" class="card" img_class="object-cover" }}
```

### Captions

[](#captions)

In figure mode, the tag auto-captions from the resolved `alt` text (which itself falls back to the asset's `alt` field). Explicit `caption` wins. Pass `caption="false"` to render a `` without a ``.

```
{{# Auto-caption from alt (figure=true) #}}
{{ responsive_image :src="hero" alt="Sunset over the coast" figure="true" }}
{{# → …Sunset over the coast #}}

{{# Explicit caption #}}
{{ responsive_image :src="hero" figure="true" caption="Shot in Lisbon" }}

{{# Figure without caption #}}
{{ responsive_image :src="hero" figure="true" caption="false" }}
```

### Focal point → `object-position`

[](#focal-point--object-position)

When the source is a Statamic asset with a focal point set in the CP, the tag emits an inline `object-position: x% y%` on the `` so CSS-cropped layouts (e.g. `object-fit: cover` on a fixed-aspect container) keep the subject in frame. This is on by default, is a no-op when no focal point is set, and costs nothing when the CSS doesn't use `object-fit`.

Tag alias
---------

[](#tag-alias)

For brevity, the addon ships a short alias `{{ pic }}` alongside the canonical `{{ responsive_image }}`. Both tags share every behavior, parameter, and wildcard form:

```
{{ pic :src="hero" alt="A sunset" }}
{{ pic:hero alt="A sunset" }}
```

The alias handle is configurable:

```
// config/responsive-images.php
'tag_alias' => 'pic',   // set to any handle, or null to disable
```

Wildcard form
-------------

[](#wildcard-form)

Resolve `src` from the template context by field name:

```
{{ responsive_image:hero }}
{{ pic:hero alt="Custom alt" }}
```

The tag suffix (after the `:`) is read from `$this->context`, so any field, augmented asset, or template variable on the current scope is usable.

Preload
-------

[](#preload)

For above-the-fold images (LCP candidates), set `preload="true"`:

```
{{ pic :src="hero" alt="…" preload="true" }}
```

The tag pushes a `` onto the Antlers `head` stack. Statamic's default head partial (`vendor/statamic/cms/resources/views/partials/head.blade.php`) already renders that stack, so preload works out of the box on a stock layout.

If you've replaced the default head partial with a fully custom one, make sure it still renders the stack:

```

    {{ stack name="head" }}

```

When the stack is absent, Statamic silently discards the push — no error, but also no preload link in the output.

When `preload="true"` is set, the tag also:

- Sets `loading="eager"` on the `` (unless you passed `loading="..."` explicitly).
- Sets `fetchpriority="high"` on the `` (unless you passed `fetchpriority="..."` explicitly).

Both auto-behaviors are togglable in config:

```
'preload' => [
    'auto_eager'    => true,
    'auto_priority' => true,
],
```

**Format selection.** The preload link targets the highest-priority enabled format (AVIF → WebP → fallback). Browsers that can't decode the format (e.g. older browsers on an AVIF link) skip the preload — safe, because `type=` is set.

**Limitations.**

- Per-breakpoint preload for art-directed sources is not supported in v1 — the preload targets the primary `src`.
- Needs a rendered `head` stack in your layout. Statamic's default partial provides this; only custom layouts that omit it would need to wire it in manually.

SVG and GIF
-----------

[](#svg-and-gif)

SVG (`image/svg+xml`) and GIF (`image/gif`) sources skip the Glide pipeline entirely. The tag emits a plain `` with the original URL, `width`/`height` from metadata when available, `class`, `loading`, `decoding`, and `aria-hidden="true"` when `alt` is empty. No ``, no `srcset`, no re-encoding — raster transforms would either produce meaningless output (SVG) or lose animation (GIF).

Glide passthrough params (`blur`, `sharpen`, etc.) are ignored for these sources.

Art direction
-------------

[](#art-direction)

Pass an array of entries via the `sources` parameter. Each entry becomes its own block of `` elements with the given `media` query. Entries earlier in the array win (the browser picks the first matching ``).

**Antlers limitation — sources must be a variable, not an inline literal.** Antlers' expression parser chokes on inline array literals whose string values contain colons (e.g. `(max-width: 768px)`), so you cannot pass `:sources="[{...}]"` directly in a template. Build the array outside the template and pass it by name. The cleanest options:

1. **Blueprint field.** Add a `replicator` or `grid` field called `image_sources` with `src`, `media`, `sizes`, and `ratio` subfields, then:

    ```
    {{ responsive_image :src="hero_desktop" :sources="image_sources" }}
    ```
2. **Template variable via a view composer, augmenter, or controller.** Share a `hero_sources` array from PHP and reference it the same way:

    ```
    {{ responsive_image :src="hero_desktop" :sources="hero_sources" }}
    ```

Each entry accepts `src` (required), `media`, `sizes`, and `ratio`. The last entry's `src` is used as the `` fallback when no breakpoint matches.

Config
------

[](#config)

`config/responsive-images.php`:

```
return [
    'device_sizes'   => [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    'image_sizes'    => [16, 32, 48, 64, 96, 128, 256, 384],
    'default_sizes'  => '(min-width: 1280px) 640px, (min-width: 768px) 50vw, 90vw',
    'tag_alias'      => 'pic',
    'fallback_width' => 828,

    'formats' => [
        'avif'     => ['enabled' => true, 'quality' => 50],
        'webp'     => ['enabled' => true, 'quality' => 75],
        'fallback' => ['quality' => 82],
    ],

    'placeholder' => [
        'enabled' => true,
        'width'   => 32,
        'blur'    => 40,
        'quality' => 40,
    ],

    'preload' => [
        'auto_eager'    => true,
        'auto_priority' => true,
    ],

    'glide' => [
        'default_fit' => 'crop_focal',
    ],

    'cache' => [
        'store'        => null,
        'prefix'       => 'respimg',
        'metadata_ttl' => 7_776_000,
        'sentinel_ttl' => 60,
    ],
];
```

**`device_sizes` vs `image_sizes`.** Device sizes cover full-width images at common device breakpoints. Image sizes cover small images (thumbnails, icons). The final srcset pool is their union, deduped, sorted, and capped at the source image's intrinsic width — the browser then picks the best candidate based on `sizes`.

**Format quality.** AVIF defaults to 50, WebP to 75, fallback to 82. Lower values ship smaller bytes; tune per project.

**Placeholder integration with `daun/statamic-placeholders`.** If you install the [`daun/statamic-placeholders`](https://github.com/daun/statamic-placeholders) addon, its placeholder data (ThumbHash, BlurHash, or Average color — whichever you've configured on the asset's `placeholder` field) is auto-detected and used in preference to the built-in Glide LQIP. When the asset has no placeholder data or when `src` is a raw URL, we silently fall back to the Glide LQIP — output shape is unchanged (still a base64 data URI on `background-image`). Provider choice lives entirely in that addon; we don't expose a provider knob, since mismatching our override against the blueprint's `placeholder_type` would silently miss and fall back. Disable the integration by setting `placeholder.statamic_placeholders.enabled` to `false`.

Performance notes
-----------------

[](#performance-notes)

- **Metadata is cached** by `{prefix}:meta:{id}:{mtime}`. Changing the source file invalidates automatically.
- **LQIPs are cached** by `{prefix}:lqip:{id}:{mtime}` and inlined as a base64 data URI (no extra request).
- **Glide does the heavy lifting** — transformed variants are cached on disk by Statamic's Glide pipeline. Cold requests do the work once.
- **No `Accept` sniffing.** Format negotiation is entirely browser-side via ``, so the response is cacheable by any CDN.

Caveats
-------

[](#caveats)

- **AVIF encoding is slow.** First request per (image, width) can take a few seconds. Warm critical pages at deploy time if this matters.
- **AVIF requires Imagick with libheif** or a recent GD build. If your image driver can't emit AVIF, disable it:

    ```
    'formats' => ['avif' => ['enabled' => false, 'quality' => 50]],
    ```
- **Non-image assets are skipped.** Unresolvable `src` values log a warning and render nothing.
- **Plain URL `src` values bypass mtime-based cache invalidation.** Only Statamic assets (`assets::id` or asset instances) carry an mtime; URL strings cache under a zero mtime, so replacing a file at the same URL will not invalidate metadata or LQIPs. Clear the cache manually (or bump the config `cache.prefix`) after such replacements.
- **Missing alt text is logged** but does not throw. Fix the warning or pass `alt=""` explicitly for decorative images.
- **Color profiles are normalized to sRGB** on the Imagick driver with the `lcms` delegate. Source images in Adobe RGB, Display P3, or CMYK are color-managed (not blindly tagged) so the browser gets true sRGB output. The manipulator silently no-ops on GD, non-Imagick drivers, and Imagick builds without `lcms` — delivery continues unchanged in those cases. Confirm `lcms` availability with `convert -list configure | grep DELEGATES`. **After upgrading, clear `storage/statamic/glide`** so existing cached transforms get regenerated with the profile applied.

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance90

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c206f9572831d780b071eda0e61218660a9ff24db838a0ba7d71863b5b54801?d=identicon)[massif-web](/maintainers/massif-web)

---

Top Contributors

[![ynamite](https://avatars.githubusercontent.com/u/1733051?v=4)](https://github.com/ynamite "ynamite (28 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/massif-statamic-responsive-images/health.svg)

```
[![Health](https://phpackages.com/badges/massif-statamic-responsive-images/health.svg)](https://phpackages.com/packages/massif-statamic-responsive-images)
```

###  Alternatives

[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

135212.4k7](/packages/statamic-rad-pack-runway)[statamic/seo-pro

68488.6k](/packages/statamic-seo-pro)[rias/statamic-redirect

29322.9k](/packages/rias-statamic-redirect)[justbetter/statamic-image-optimize

Image optimization after upload

1318.4k](/packages/justbetter-statamic-image-optimize)[visuellverstehen/statamic-picturesque

A Statamic tag for building HTML-only responsive images.

1117.7k](/packages/visuellverstehen-statamic-picturesque)[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3310.1k](/packages/duncanmcclean-statamic-cargo)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
