Visual reference uses inline-SVG placeholders since the preview ships without external image assets.
/*
* Image — foundation
*
* Responsive image with optional <picture> source set. CSS-only — no
* runtime, no JavaScript.
*
* Per answered design question: native <picture>, loading="eager"
* default, opt-in lazy via data-lazy="true". The component is the
* <img>; <picture> is an author wrapper choice.
*
* <img class="image" src="hero.jpg" alt="…" width="1200" height="600" />
*
* <picture>
* <source srcset="hero.avif" type="image/avif" />
* <source srcset="hero.webp" type="image/webp" />
* <img class="image" src="hero.jpg" alt="…" width="1200" height="600" />
* </picture>
*
* <img class="image -rounded" src="avatar.jpg" alt="…" data-lazy="true" />
*/
@layer malevich.components {
.image {
display: block;
max-inline-size: 100%;
block-size: auto;
/* Prevent CLS — authors should set width/height attributes too,
* which sets the intrinsic ratio. This rule keeps that ratio when
* the rendered size is constrained.
*/
object-fit: cover;
object-position: center;
}
.image[data-lazy="true"] {
/* Marker for tooling; browsers handle native loading via
* loading="lazy" attribute, not CSS. Authors pair data-lazy="true"
* with loading="lazy" on the <img>.
*/
}
.image.-rounded {
border-radius: var(--radius-card);
}
.image.-pill {
border-radius: var(--radius-pill);
}
.image.-circle {
border-radius: 50%;
aspect-ratio: 1;
}
.image.-cover {
object-fit: cover;
inline-size: 100%;
block-size: 100%;
}
.image.-contain {
object-fit: contain;
}
.image.-bordered {
border: var(--border-width-hairline) solid var(--color-border-default);
}
.image.-shadow {
box-shadow: var(--shadow-raised);
}
}
This component is pure CSS — no JavaScript required.
# Image — AGENTS.md
> Auto-generated from `image.docs.md` + the modifier applicability matrix.
> Edit those source files; this file regenerates on build.
## Identity
- **Component:** `image`
- **Layer:** foundations
- **Status:** stable
- **Last updated:** 2026-05-19
## Summary
- Display a single image with the design system's visual conventions
## Variants
| Variant | Class | Use for |
|-----------|---------------------|--------------------------------------|
| Default | `.image` | Plain responsive image |
| Rounded | `.image.-rounded` | Card-radius corners |
| Pill | `.image.-pill` | Pill-shape (full radius) |
| Circle | `.image.-circle` | Circular crop (1:1 aspect) |
| Cover | `.image.-cover` | object-fit: cover, fills container |
| Contain | `.image.-contain` | object-fit: contain |
| Bordered | `.image.-bordered` | Hairline border |
| Shadow | `.image.-shadow` | Raised shadow |
Modifiers compose: `.image.-rounded.-shadow.-bordered`.
## Modifier applicability
Per ADR-0007. Modifiers attach via `data-{category}="value"` attributes.
| Category | Accepts |
|---|---|
| `background` | — |
| `surface` | `flat`, `elevated` |
| `effect` | `ring`, `glow` |
| `shader` | — |
| `rhythm` | — |
| `motion` | any value |
| `behavior` | — |
## Anatomy
```html
<!-- Plain -->
<img class="image" src="…" alt="…" width="600" height="400" />
<!-- With source negotiation -->
<picture>
<source srcset="hero.avif" type="image/avif" />
<source srcset="hero.webp" type="image/webp" />
<img class="image -rounded -shadow" src="hero.jpg" alt="…"
width="1200" height="600" />
</picture>
<!-- With srcset for density / size -->
<img class="image -cover"
src="card.jpg"
srcset="card.jpg 1x, card@2x.jpg 2x"
alt="…"
width="400" height="400" />
<!-- Avatar-style circle -->
<img class="image -circle -bordered"
src="user.jpg" alt="Alex K." width="64" height="64" />
```
## Tokens consumed
### From semantic tier
- `--color-border-default` — bordered variant
- `--radius-card` — rounded variant
- `--radius-pill` — pill variant
- `--shadow-raised` — shadow variant
- `--border-width-hairline` — border thickness
## Accessibility contract
`<img>` requires a meaningful `alt` attribute:
- For informative images: describe the content.
- For decorative images: use `alt=""` (empty). Do NOT omit the attribute.
- For complex images (charts, diagrams): use `alt` for short summary
and a longer description in surrounding text or via `aria-describedby`.
The component does not impose any alt-text behavior; it is the
author's responsibility. The linter will eventually flag `<img
class="image">` without `alt` (v1.1).
To prevent cumulative layout shift (CLS), always set `width` and
`height` attributes on `<img>` so the browser reserves the correct
aspect ratio. The CSS sets `block-size: auto` so the rendered size
scales while preserving the intrinsic ratio.
## Guidance
### Do
- Always set `width` and `height` attributes.
- Always set `alt` (use `alt=""` for decorative).
- Wrap in `<picture>` when you have multiple source formats.
- Pair `data-lazy="true"` with `loading="lazy"`.
### Don't
- Don't omit `alt`.
- Don't lazy-load above-the-fold images — it delays Largest
Contentful Paint.
- Don't use this component for background images. Use CSS
`background-image` on a div.