A responsive, lazy-loaded image — wraps astro:assets Image with sensible defaults.

Last updated

Uses JavaScript

Only when a fallback slot is present: a small island listens for the image’s error event and swaps the image for the fallback in the same box, carrying the alt text over as an accessible name.

Without JS: The broken image shows its alt text; the fallback stays hidden.

Placeholder
Show code
<Image src={placeholder} alt="Placeholder" />

Add

npx @ava-ui/apogee add image

No Apogee dependencies. Wraps Astro’s built-in astro:assets Image — not an added dependency.

Usage

---
import Image from '@/components/apogee/Image/Image.astro'
import hero from '../assets/hero.jpg'
---
<Image src={hero} alt="Team meeting around a table" />

API

@composable — exposed to Content Editors in AVA. Any other astro:assets Image prop — width, height, widths, sizes, format, quality, densities, … — passes through.

Prop Type Default Description
src ImageMetadata | string — Required. A local image import (optimised at build) or a remote URL (sized with inferSize unless width/height are given).
alt string — Required. Alt text ("" for decorative images).
loading 'lazy' | 'eager' 'lazy' Native lazy-loading. Use eager for the page’s LCP image.
decoding 'async' | 'sync' | 'auto' 'async' Native image decoding hint.
width / height number — Intrinsic size; sets the aspect ratio and the largest generated width.
widths number[] — Responsive candidates for srcset, e.g. [480, 800, 1200].
sizes string — The sizes attribute that picks a candidate; pair it with widths.
quality 'low' | 'mid' | 'high' | 'max' | number — Compression of optimised local images.
format 'avif' | 'webp' | 'png' | 'jpeg' | 'svg' — Output format of optimised local images (Astro defaults to webp).
fit 'cover' | 'contain' — How the image fills a box sized by width/height or a class.
radius 'sm' | 'md' | 'lg' | 'xl' | 'full' — Corner radius token.
outline boolean false Inset hairline for images that blend into the page.
class string — Extra classes, merged last (max-w-full always, plus h-auto unless fit is set).

Slot fallback: shown in the image’s box when it fails to load (a small island swaps them; without JS the broken image shows its alt text).

Fit and radius

fit decides what happens when the box and the image disagree: cover crops, contain letterboxes. Both boxes below are square; the image is 16:9.

CoverContain
Show code
<Image src={placeholder} alt="Cover" width={320} height={180} fit="cover" class="size-40" />
<Image src={placeholder} alt="Contain" width={320} height={180} fit="contain" class="size-40 bg-neutral-faded" />

Every radius:

Radius smRadius mdRadius lgRadius xlRadius full
Show code
<Image src={placeholder} alt="Radius sm" width={96} height={96} fit="cover" radius="sm" class="size-24" />
<Image src={placeholder} alt="Radius md" width={96} height={96} fit="cover" radius="md" class="size-24" />
<Image src={placeholder} alt="Radius lg" width={96} height={96} fit="cover" radius="lg" class="size-24" />
<Image src={placeholder} alt="Radius xl" width={96} height={96} fit="cover" radius="xl" class="size-24" />
<Image src={placeholder} alt="Radius full" width={96} height={96} fit="cover" radius="full" class="size-24" />

States

  • Loading — loading="lazy" by default; set eager on the page’s main image.
  • Broken — with a fallback slot, a failed load swaps in the fallback in the same box. Without JS, or without the slot, the browser shows the alt text.

Examples

Eager-loaded hero image, so it isn’t deferred:

<Image src={hero} alt="Product screenshot" loading="eager" />

Responsive sizing:

<Image
  src={hero}
  alt="Product screenshot"
  widths={[400, 800, 1200]}
  sizes="(min-width: 768px) 50vw, 100vw"
/>

Format + quality override:

<Image src={hero} alt="Product screenshot" format="avif" quality={80} />

Remote source, fit and radius, outline, and a fallback for a broken URL:

A landscapeA landscapeMissing image
Show code
<Image src="https://picsum.photos/seed/apogee/320/200" alt="A landscape" width={320} height={200} radius="lg" />
<Image src="https://picsum.photos/seed/apogee/320/200" alt="A landscape" width={160} height={160} fit="cover" radius="full" outline />
<Image src="https://example.invalid/missing.jpg" alt="Missing image" width={160} height={100} radius="md">
<div slot="fallback" class="grid place-items-center rounded-md bg-neutral-faded text-sm text-neutral-faded">No image</div>
</Image>

Accessibility

  • alt is required — describe the image’s content or purpose; pass alt="" only for purely decorative images.
  • loading="lazy" (the default) defers offscreen images without JS. Set loading="eager" on the page’s LCP image so it isn’t deferred.
  • No JS — astro:assets processes the image ahead of time; output is a plain <img>.

Tokens

None. Image has no themeable colour/spacing tokens of its own — h-auto max-w-full keeps it responsive by default; override sizing via class, width, or height.

Do / Don’t

  • Do always import a local image (ImageMetadata) so astro:assets can optimize it — don’t pass a raw URL string.
  • Do set loading="eager" on the one image that’s the page’s LCP; leave everything else lazy.
  • Don’t upscale a small source image via width/height — astro:assets will warn or error.
  • Don’t skip alt — pass alt="" explicitly for decorative images rather than omitting it.

Content

  • Alt text describes what’s in the image or its purpose (not “image of…”), kept short, no ending period unless it’s a full sentence.
  • Decorative images get alt="", never omitted.