withastro/astro · error · AstroError

ImageMissingAlt

ImageMissingAlt

Error message

Image missing "alt" property. "alt" text is required to describe important images on the page.

What it means

The built-in `<Picture />` component enforces the same accessibility rule as `<Image />`: `alt` is required and `ImageMissingAlt` is thrown when it is `undefined`/`null`. Note the check inspects the component props, not `pictureAttributes` — putting alt on the wrapping `<picture>` element does not satisfy it.

Solutions

  1. Pass alt as a top-level prop: `<Picture src={hero} formats={['avif','webp']} alt="Portrait of the founder" />`
  2. Use `alt=""` explicitly for decorative images
  3. Check that wrapper-level attributes go in `pictureAttributes` but alt goes on the component itself

Example fix

// before
<Picture src={hero} formats={['avif', 'webp']} pictureAttributes={{ alt: 'Hero' }} />

// after
<Picture src={hero} formats={['avif', 'webp']} alt="Hero" pictureAttributes={{ loading: 'eager' }} />
Defensive patterns

Strategy: type-guard

Type guard

function hasAlt(props: { alt?: string | null }): boolean {
  return props.alt != null;
}

Prevention

When it happens

Trigger: Rendering `<Picture src={...} formats={[...]} />` without alt; passing `alt` inside `pictureAttributes={{ alt: '...' }}` (which targets the `<picture>` wrapper) instead of as a top-level prop; destructured props where alt was not forwarded.

Common situations: Migrating from `<Image>` to `<Picture>` and misplacing attributes; data-driven components that spread `pictureAttributes` and forget the top-level alt; same CMS-missing-alt cases as `<Image>`.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/de8dada9e6ad4427. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/components/Picture.astro:38

	formats?: ImageOutputFormat[];
	fallbackFormat?: ImageOutputFormat;
	pictureAttributes?: HTMLAttributes<'picture'>;
};

const defaultFormats = ['webp'] as const;
const defaultFallbackFormat = 'png' as const;

// Certain formats don't want PNG fallbacks:
// - GIF will typically want to stay as a gif, either for animation or for the lower amount of colors
// - SVGs can't be converted to raster formats in most cases
// - JPEGs compress photographs and high-noise images better than PNG in most cases
// For those, we'll use the original format as the fallback instead.
const specialFormatsFallback = ['gif', 'svg', 'jpg', 'jpeg'] as const;

const { formats = defaultFormats, pictureAttributes = {}, fallbackFormat, ...props } = Astro.props;

if (props.alt === undefined || props.alt === null) {
	throw new AstroError(AstroErrorData.ImageMissingAlt);
}

// Picture attribute inherit scoped styles from class and attributes
const scopedStyleClass = props.class?.match(/\bastro-\w{8}\b/)?.[0];
if (scopedStyleClass) {
	if (pictureAttributes.class) {
		pictureAttributes.class = `${pictureAttributes.class} ${scopedStyleClass}`;
	} else {
		pictureAttributes.class = scopedStyleClass;
	}
}

const layout = props.layout ?? imageConfig.layout ?? 'none';
const useResponsive = layout !== 'none';

if (useResponsive) {
	props.layout ??= imageConfig.layout;
	props.fit ??= imageConfig.objectFit ?? 'cover';

View on GitHub (pinned to 52e6c34790)