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.
| Layout | Behavior | Typical use |
|---|---|---|
constrained | Scales down to fit the container but never grows past its intrinsic width | Article and content images |
full-width | Always fills the container width (e.g. hero banners) | Full-bleed heroes |
fixed | Stays at exact width/height, only varying by density (1x/2x) | Logos, icons, avatars |
none | Disables responsive behavior; you supply srcset/sizes manually | Full 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
sizesmatch the rendered width set by your CSS. A mismatchedsizesis 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
densitiesorwidths/layout, not both. Density descriptors and width descriptors cannot be combined in a singlesrcset, 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
layoutinastro:assetsconfig so every image is responsive by default, then override only where needed. - Choose
constrainedfor content images,full-widthfor heroes, andfixed(withdensities) for logos and icons. - Always supply an accurate
sizesthat matches your real CSS render width to avoid over- or under-fetching. - Use
densitiesfor fixed-size UI imagery andwidths/layoutfor fluid imagery — never mix the two on one element. - Prefer
<Picture />withformats={["avif", "webp"]}to ship modern formats with a safe fallback. - Keep explicit
widthandheightset so the browser reserves space and avoids layout shift (CLS).