# Image

**When to use:** An `<img>` that holds its place while it loads: a skeleton the image's size, or its thumbnail, then the image, and a fallback if it fails. It renders the `<img>` itself rather than wrapping it, so it drops in wherever an `<img>` goes, `PanZoom` included. For a profile picture with initials, use `Avatar`; for a full-screen set, `Lightbox`, which uses Image for each of its own.

**Keywords:** picture, photo, lazy, placeholder, blur up, broken image, loading state, layout shift

**Import:** `import { Image } from '@ahrowe/ui'`  
**Types:** `import type { ImageProps } from '@ahrowe/ui'`

```tsx
import { Image } from '@ahrowe/ui';

// A skeleton of 1200 x 800's shape until it loads, stretched to its container like the image
<Image src='/photos/harbour.jpg' alt='Harbour at dawn' width={1200} height={800} style={{ width: '100%', height: 'auto' }} />

// The thumbnail your list already shows, until the full image is in
<Image src='/photos/harbour.jpg' thumbnail='/photos/harbour-small.jpg' alt='Harbour at dawn' width={1200} height={800} />

// Lazily, in a long list
<Image src={product.photo} alt={product.name} width={320} height={320} loading='lazy' />

// Your own stand-in and fallback, or none at all while it loads
<Image src={url} alt='Chart' placeholder={<Loader />} fallback={<p>No chart yet</p>} />
<Image src={url} alt='Chart' placeholder={null} />

// Zoomable, with a loading state: the image stays a direct child of PanZoom
<PanZoom style={{ height: 320 }}>
  <Image src='/plans/ground-floor.png' thumbnail='/plans/ground-floor-small.png' alt='Ground floor plan' />
</PanZoom>
```

**How it works:** the `<img>` is the component. `ref`, `className`, `style` and every native attribute land on it, `srcSet`, `sizes`, `loading` and `decoding` included. While it loads, a stand-in takes its place beside it, without a wrapper: a skeleton, or your `placeholder` instead, until the `thumbnail` has loaded, then the thumbnail. The image and the thumbnail meanwhile load out of sight, at no size and out of the layout, and each replaces the stand-in before it once it has loaded; the image fades in, unless what it replaces is its own thumbnail. While it loads, the image carries `aria-busy`, which PanZoom reads too: it has nothing to zoom until the image is in. An image the browser already has shows at once, with no stand-in at all. If it fails, `fallback`, or a quiet box with an image icon named after `alt`, takes its place for good.

**Size:** a skeleton or a fallback has no picture to take a size from, so it takes the image's: from its `width` and `height` attributes, or from the CSS that sizes the image, since `className` and `style` apply to the stand-in too. A class that sizes the image wins over the attributes, as it does on any `<img>`, and an aspect ratio follows from `width` and `height` the way it does for the image. With none of these, a stand-in takes the image's own size once the browser knows it, from the image's first bytes rather than its last, keeping its proportions under CSS that sets only a width. Until then it has no size, so a skeleton shows only from that moment. This happens only while the image is near the screen, so a lazy one further down costs nothing until it is. A thumbnail sizes itself like the image would; one in another aspect ratio (a square crop) jumps to the right shape when the image replaces it.

**Lazy loading:** `loading` is the browser's, and so is its default, `eager`. Set `loading='lazy'` for images in lists and grids, and leave it off for the one at the top of the page, which a lazy load would only delay. A lazy image loads once its stand-in nears the viewport.

**While it loads, there are two elements:** the stand-in and the image. The image is taken out of the layout, so it takes no grid or flex place, but CSS that counts children (`:only-child`) sees both. The `ref` points at the `<img>` throughout, and is `null` once a fallback has replaced it.

**Accessibility:** `alt` is required: describe the picture, or pass `''` for a decorative one. The image is in the page from the start, so a screen reader reads its `alt` while it loads, with `aria-busy` saying it is still on its way. The built-in fallback is named after `alt` too, and hidden from screen readers for a decorative image.

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `src` | `string` | The image |
| `alt` | `string` | Its description, or `''` for a decorative image |
| `thumbnail` | `string` | A small version shown in its place until it has loaded |
| `placeholder` | `ReactNode` | Shown while it loads instead of the skeleton, until a `thumbnail` has loaded; `null` for nothing |
| `fallback` | `ReactNode` | Shown if it fails, instead of the built-in box |
| `width`, `height` | `number` | The image's size, which a stand-in takes before the image has one |

**Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Image: { decoding: 'async' } }}`. See [ConfigProvider.md](ConfigProvider.md).

**Slots:** `placeholder` `fallback` — the built-in stand-ins. The image itself is `className` and `style`.
