Image
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.

Show codeHide 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.


Show codeHide 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:





Show codeHide 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; seteageron the page’s main image. - Broken — with a
fallbackslot, 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:

Show codeHide 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
altis required — describe the image’s content or purpose; passalt=""only for purely decorative images.loading="lazy"(the default) defers offscreen images without JS. Setloading="eager"on the page’s LCP image so it isn’t deferred.- No JS —
astro:assetsprocesses 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) soastro:assetscan 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 elselazy. - Don’t upscale a small source image via
width/height—astro:assetswill warn or error. - Don’t skip
alt— passalt=""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.