Type something to search...
How to use the Picture component for responsive images in Astro?

How to use the Picture component for responsive images in Astro?

To use the Picture component in Astro, import Picture from astro:assets, import your image from src/, and render <Picture src={image} alt="..." formats={['avif', 'webp']} />. Astro generates a <picture> element with one <source> per format, in the order you list them, plus an <img> fallback in the original format. The browser picks the first format it supports, so modern browsers get small AVIF files and older ones still get a JPEG or PNG. Add widths and sizes, or a layout, and every format gets its own responsive srcset too.

<Picture /> accepts every prop that <Image /> does, so if you already know the Image component, you know most of this one. The difference is that <Image /> outputs a single format, while <Picture /> lets the browser choose between several. In this article you'll learn when to use Picture instead of Image, the exact HTML it generates, how to set formats and fallbacks, how to make it responsive, how to style it, how to load it with high priority, and how to handle art direction, which Picture doesn't do on its own.

Picture vs Image

Both components optimize images from src/, require alt, set dimensions and lazy load by default. The choice comes down to formats:

<Image /><Picture />
OutputOne <img><picture> with <source> tags and an <img>
FormatsOne (WebP by default)Several, plus a fallback
Default outputWebPWebP source plus original-format fallback
Responsive srcsetYesYes, per format
HTML weightSmallestSlightly larger
Best forMost imagesWhen you want AVIF with a safe fallback

WebP is supported in every current browser, so for many sites <Image /> is all you need. <Picture /> is worth it when you want AVIF's extra savings on large photos while guaranteeing that every browser can show something. The post on optimizing images with the Astro Image component covers the shared props in detail. All the output below comes from astro build on Astro 7.4.2.

Your First Picture

---
// src/pages/index.astro
import { Picture } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Picture src={hero} alt="Sunrise" width={1200} formats={['avif', 'webp']} />
<picture>
  <source srcset="/_astro/hero.BXpjeiMQ_Z1Tr7E9.avif" type="image/avif">
  <source srcset="/_astro/hero.BXpjeiMQ_ZFTsfW.webp" type="image/webp">
  <img src="/_astro/hero.BXpjeiMQ_1n8sus.jpg" alt="Sunrise" loading="lazy" decoding="async" width="1200" height="675">
</picture>

Here's how the browser reads that:

  1. It checks each <source> from top to bottom and uses the first type it supports.
  2. A browser with AVIF support downloads the .avif file and ignores the rest.
  3. A browser without AVIF but with WebP downloads the .webp file.
  4. A browser that supports neither uses the <img>'s src, the JPEG.

Only one file is downloaded. The <img> element is always the one that's displayed; the <source> elements just change which file it shows. That's why alt, width, height and the loading attributes all sit on the <img>.

Order Matters

List formats from most efficient to least. If you wrote formats={['webp', 'avif']}, every browser that supports WebP, which is all of them, would stop at the first source and AVIF would never be used.

Formats and Fallbacks

Two props control what's generated:

  • formats: the array of <source> formats. The default is ['webp'].
  • fallbackFormat: the format of the <img> fallback. By default it's the original format for JPEGs, PNG for most other sources, GIF for animated GIFs and SVG for SVGs.

So a bare <Picture src={hero} alt="Hero" width={600} /> gives you a WebP source and a JPEG fallback:

<picture>
  <source srcset="/_astro/hero.BXpjeiMQ_ZglFb9.webp" type="image/webp">
  <img src="/_astro/hero.BXpjeiMQ_Zmi2fc.jpg" alt="Hero" loading="lazy" decoding="async" width="600" height="338">
</picture>

For a logo or illustration with transparency, keep a PNG fallback so it never gains a solid background:

<Picture
  src={logo}
  alt="Logo"
  width={200}
  formats={['avif', 'webp']}
  fallbackFormat="png"
  pictureAttributes={{ class: 'logo' }}
/>
<picture class="logo">
  <source srcset="/_astro/logo.BLtt58g0_hne4k.avif" type="image/avif">
  <source srcset="/_astro/logo.BLtt58g0_Lnhzy.webp" type="image/webp">
  <img src="/_astro/logo.BLtt58g0_Z14U8uI.png" alt="Logo" loading="lazy" decoding="async" width="200" height="200">
</picture>

Simple flat graphics are often better as SVG, which doesn't need <Picture /> at all.

Making Picture Responsive

widths and sizes

Add widths and sizes exactly as you would with <Image />. Each format gets the full set of widths:

<Picture
  src={hero}
  alt="Hero"
  formats={['avif', 'webp']}
  widths={[480, 960, 1600]}
  sizes="(max-width: 700px) 100vw, 960px"
/>
<picture>
  <source srcset="/_astro/hero..._1DEWdu.avif 480w, /_astro/hero..._Z1gY59b.avif 960w, /_astro/hero..._1H1z5r.avif 1600w" type="image/avif" sizes="(max-width: 700px) 100vw, 960px">
  <source srcset="/_astro/hero..._1m3P59.webp 480w, /_astro/hero..._Z1yAchw.webp 960w, /_astro/hero..._Z29CTki.webp 1600w" type="image/webp" sizes="(max-width: 700px) 100vw, 960px">
  <img src="/_astro/hero..._2nRG4I.jpg" srcset="/_astro/hero..._1g7t16.jpg 480w, /_astro/hero..._Z1Ewylz.jpg 960w, /_astro/hero..._Z5zXyS.jpg 1600w" alt="Hero" sizes="(max-width: 700px) 100vw, 960px" loading="lazy" decoding="async" width="2400" height="1350">
</picture>

Three widths in three formats means nine files. The browser first picks a format from the type, then uses sizes and the screen's pixel density to pick a width from that source's srcset.

Write sizes to match your CSS. (max-width: 700px) 100vw, 960px says "on narrow screens the image fills the viewport; otherwise it's 960px wide". If sizes is wrong, the browser downloads the wrong file: too big wastes data, too small looks blurry.

layout

The layout prop calculates srcset and sizes for you, which is easier to get right:

<Picture
  src={hero}
  alt="Hero"
  width={1200}
  height={675}
  formats={['avif']}
  layout="full-width"
/>
<picture>
  <source srcset="... 640w, ... 750w, ... 828w, ... 1080w, ... 1280w, ... 1668w, ... 2048w" type="image/avif">
  <img src="/_astro/hero..._1K0jUs.jpg" srcset="... 640w, ... 750w, ... 828w, ... 1080w, ... 1280w, ... 1668w, ... 2048w" alt="Hero" loading="lazy" decoding="async" sizes="100vw" data-astro-image="full-width" data-astro-image-fit="cover" data-astro-image-pos="center" width="1200" height="675">
</picture>

full-width uses sizes="100vw" and a set of common screen widths, which suits hero banners. constrained suits images in a content column: it never displays wider than the width you give. If you want every image on the site to use a layout, set image.layout in astro.config.mjs; with image.responsiveStyles: true, Astro also adds the CSS that makes each layout behave correctly.

Responsive pictures multiply the number of files Astro generates, so builds take longer the first time. Processed images are cached in node_modules/.astro, so later builds only process new or changed images.

Styling Picture

Props that aren't Picture-specific, such as class, style and data-* attributes, go on the inner <img>. Astro's scoped styles still work:

---
import { Picture } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Picture src={hero} alt="Hero" width={600} class="rounded" />

<style>
  .rounded {
    border-radius: 1rem;
  }
</style>
<style>.rounded[data-astro-cid-3o5zjrt4]{border-radius:1rem}</style>
<picture data-astro-cid-3o5zjrt4="true">
  <source srcset="/_astro/hero.BXpjeiMQ_ZglFb9.webp" type="image/webp">
  <img src="/_astro/hero.BXpjeiMQ_Zmi2fc.jpg" alt="Hero" data-astro-cid-3o5zjrt4="true" loading="lazy" decoding="async" width="600" height="338" class="rounded">
</picture>

To style the <picture> element itself, for example to make it a flex item or give it an aspect ratio, pass attributes through pictureAttributes, as in the logo example above. A useful pattern is to make the image fill its wrapper:

picture.cover img {
  width: 100%;
  height: auto;
  display: block;
}

Keeping height: auto with the width and height attributes in place lets the browser compute the aspect ratio before the image loads, which prevents layout shift.

High-Priority Pictures

The same priority prop from <Image /> works here and goes on the <img>, because that's the element the browser actually loads:

<Picture src={hero} alt="Hero" formats={['avif', 'webp']} widths={[480, 960, 1600]} sizes="(max-width: 700px) 100vw, 960px" priority />
<img ... loading="eager" decoding="sync" fetchpriority="high" width="2400" height="1350">

Use it for the hero image or main product photo, the element most likely to be your Largest Contentful Paint. Everything else should stay lazy, as explained in lazy loading images and components in Astro.

Art Direction

Art direction means showing a different image, not just a different size, depending on the screen: a wide landscape crop on desktop and a tight square crop on mobile. In HTML that's done with <source media="...">.

<Picture /> doesn't generate media sources; its sources differ only by format. For art direction, write the <picture> element yourself and use getImage() to produce the optimized files:

---
// src/components/HeroArt.astro
import { getImage } from 'astro:assets';
import hero from '../assets/hero.jpg';
import heroSquare from '../assets/hero-square.jpg';

const wide = await getImage({ src: hero, width: 1600, format: 'webp' });
const square = await getImage({ src: heroSquare, width: 800, format: 'webp' });
---
<picture>
  <source media="(min-width: 768px)" srcset={wide.src} type="image/webp" />
  <source srcset={square.src} type="image/webp" />
  <img
    src={square.src}
    alt="Sunrise"
    width={square.attributes.width}
    height={square.attributes.height}
    loading="lazy"
    decoding="async"
  />
</picture>
<picture>
  <source media="(min-width: 768px)" srcset="/_astro/hero.BXpjeiMQ_Z29CTki.webp" type="image/webp">
  <source srcset="/_astro/hero-square.BzCWNzKt_2qwIrt.webp" type="image/webp">
  <img src="/_astro/hero-square.BzCWNzKt_2qwIrt.webp" alt="Sunrise" width="800" height="800" loading="lazy" decoding="async">
</picture>

getImage() returns src, attributes (including the computed width and height) and, when you pass widths, a ready-made srcSet.attribute string. Because the two crops have different aspect ratios, the width and height on the <img> describe the mobile crop. On desktop you can override the ratio in CSS with a media query and aspect-ratio, so the space reserved still matches what's shown.

Images in public/ and Remote Images

<Picture /> follows the same rules as <Image />. Images in public/ aren't processed, so there's no point using <Picture /> for them: you'd get sources that all point at the same unoptimized file. Remote images are only converted when their host is allowed with image.domains or image.remotePatterns, and they need explicit width and height.

Common Mistakes

  1. Wrong format order: put avif before webp, or AVIF will never be chosen.
  2. Styling <picture> with class: class goes on the <img>. Use pictureAttributes for the wrapper.
  3. Missing sizes with widths: without an accurate sizes, the browser assumes the image fills the viewport and may download a file far larger than needed.
  4. Expecting art direction: <Picture /> only switches formats. Use getImage() and a hand-written <picture> for different crops.
  5. Using Picture everywhere: for small images, the extra AVIF files add build time for little gain. <Image /> with WebP is usually enough.

FAQ: The Astro Picture Component

Use Picture when you want to serve several formats, typically AVIF with a WebP or JPEG fallback, for large photos where the extra savings matter. For most other images, Image with its default WebP output is simpler and produces less HTML.

A single WebP source plus a fallback img in the original format for JPEGs, or PNG for most other sources. Pass the formats prop, for example avif then webp, to add more sources.

Use the pictureAttributes prop, for example pictureAttributes with a class key. A class passed directly to Picture is applied to the inner img element instead.

No. Its sources only differ by format. For different crops at different screen sizes, generate each crop with getImage and write your own picture element with media attributes on the source tags.

Yes. Add the priority prop and Astro sets loading eager, decoding sync and fetchpriority high on the inner img, which is the element the browser actually loads.

Yes. It accepts widths and sizes, or the layout prop with constrained, full-width or fixed, and generates a full srcset for every format as well as for the fallback image.


Conclusion

The Picture component wraps one optimized image in a <picture> element with a <source> for each format you request and an <img> fallback, so every browser downloads the most efficient file it can display. List formats from best to most compatible, keep a PNG fallback for transparent images, and combine it with widths and sizes or a layout to make every format responsive.

Remember that attributes like class and priority land on the inner <img>, pictureAttributes targets the wrapper, and art direction needs a hand-written <picture> built with getImage(). Used selectively on large, important photos, <Picture /> gives you AVIF's savings without leaving any visitor with a broken image.

Tags :
Share :

Related Posts

What are Astro Actions and how do they work?

What are Astro Actions and how do they work?

Astro Actions are type-safe backend functions that you define once on the server and then call from the browser as if they were ordinary async fu

Dive Deeper
What are Astro adapters and which one should you choose?

What are Astro adapters and which one should you choose?

An Astro adapter is an integration that builds the server part of your project for a specific runtime, such as Node.js, Vercel Functions, Netlify

Dive Deeper
How to build API endpoints in Astro?

How to build API endpoints in Astro?

To build an API endpoint in Astro, create a .js or .ts file in src/pages/ and export a function named after an HTTP method, such as GET or

Dive Deeper