Skip to content
Astro as images 4 min read

Responsive Images

A single image rarely fits every screen. A phone, a retina laptop, and a 4K monitor all deserve a differently sized file, and shipping one large image to all of them wastes bandwidth and hurts Core Web Vitals. Astro’s responsive image support solves this by generating a srcset and sizes for you, so the browser picks the most appropriate variant for the current viewport and device pixel ratio — all at build time, with zero client-side JavaScript.

How responsive images work

The browser, not the server, decides which image to download. You give it two pieces of information and it does the rest:

  • srcset — a list of image candidates, each tagged with its intrinsic width (480w, 960w, …) or pixel density (1x, 2x).
  • sizes — a hint describing how wide the image will render at different breakpoints, so the browser can choose before layout completes.

Astro’s <Image /> and <Picture /> components can emit both automatically. When you opt in, Astro processes the source once and produces several scaled-down derivatives, wiring them into the markup for you.

Enabling responsive images globally

The simplest way to make every image responsive is to set a default layout in astro.config.mjs. This applies to all <Image /> and <Picture /> components unless overridden per-instance.

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  image: {
    // Applies a default layout + responsive styles to all images
    layout: "constrained",
  },
});

With a layout set, Astro generates the srcset, computes a sensible sizes, and injects scoped CSS so the image scales correctly within its container.

Layout modes

The layout property controls how the image behaves in its container and which srcset/sizes are generated. You can set it globally or per-image.

LayoutBehaviorTypical use
constrainedScales down to fit the container but never grows past its intrinsic widthArticle and content images
full-widthAlways fills the container width (e.g. hero banners)Full-bleed heroes
fixedStays at exact width/height, only varying by density (1x/2x)Logos, icons, avatars
noneDisables responsive behavior; you supply srcset/sizes manuallyFull manual control
---
import { Image } from "astro:assets";
import hero from "../assets/hero.jpg";
---

<Image src={hero} alt="Mountain at sunrise" layout="full-width" />

<Image src={hero} alt="Logo" layout="fixed" width={120} height={40} />

Controlling sizes manually

When a layout is active, Astro derives sizes automatically — but you know your CSS better than the build does. Override it whenever your image does not simply fill the viewport. The value mirrors the sizes attribute you would write by hand.

---
import { Image } from "astro:assets";
import photo from "../assets/photo.jpg";
---

<Image
  src={photo}
  alt="Team photo"
  layout="constrained"
  width={1200}
  height={800}
  sizes="(min-width: 1024px) 50vw, 100vw"
/>

Here the image takes the full viewport width on small screens but only half on large ones, so the browser knows not to download an oversized file inside a two-column layout.

Always make sizes match the rendered width set by your CSS. A mismatched sizes is the most common cause of the browser fetching an image that is too large or too small.

Density-aware delivery with densities

For images displayed at a fixed CSS size — logos, thumbnails, avatars — you do not need width-based candidates. Instead, serve crisper variants to high-DPR screens with densities.

---
import { Image } from "astro:assets";
import avatar from "../assets/avatar.png";
---

<Image
  src={avatar}
  alt="User avatar"
  width={64}
  height={64}
  densities={[1, 2, 3]}
/>

This produces a srcset of 64w (1x), 128w (2x), and 192w (3x) so a retina display gets a sharp image while a standard display downloads the small one.

Output:

<img
  src="/_astro/avatar.hash.png"
  srcset="/_astro/avatar.hash.png 1x, /_astro/avatar.hash_2x.png 2x, /_astro/avatar.hash_3x.png 3x"
  width="64"
  height="64"
  alt="User avatar"
  loading="lazy"
  decoding="async"
/>

Use densities or widths/layout, not both. Density descriptors and width descriptors cannot be combined in a single srcset, so Astro will ignore one if you set both.

Explicit widths and the Picture component

For maximum control you can list the exact breakpoint widths you want generated. Pair this with <Picture /> when you also want to offer modern formats like AVIF and WebP alongside a fallback.

---
import { Picture } from "astro:assets";
import banner from "../assets/banner.jpg";
---

<Picture
  src={banner}
  alt="Product banner"
  widths={[400, 800, 1200, 1600]}
  sizes="100vw"
  formats={["avif", "webp"]}
  fallbackFormat="jpg"
/>

<Picture /> emits a <picture> element with one <source> per format, each carrying its own srcset, and a final <img> fallback — letting capable browsers grab the smallest modern format while older ones still work.

Best practices

  • Set a global layout in astro:assets config so every image is responsive by default, then override only where needed.
  • Choose constrained for content images, full-width for heroes, and fixed (with densities) for logos and icons.
  • Always supply an accurate sizes that matches your real CSS render width to avoid over- or under-fetching.
  • Use densities for fixed-size UI imagery and widths/layout for fluid imagery — never mix the two on one element.
  • Prefer <Picture /> with formats={["avif", "webp"]} to ship modern formats with a safe fallback.
  • Keep explicit width and height set so the browser reserves space and avoids layout shift (CLS).
Last updated June 14, 2026
Was this helpful?