Image

Visual reference uses inline-SVG placeholders since the preview ships without external image assets.

Variants

.image .-rounded .-circle

Bordered + shadow

.-bordered .-shadow

Avatar pattern (circle + bordered)

A B C

  
/*
 * 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.