# Imagery

> Use when handling photos and illustration: aspect ratios, cropping, placeholders and blur-up, avatars, device frames, and dark-mode treatment.

- Skill: `agentsorg/imagery` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add agentsorg/imagery`
- Raw SKILL.md: https://api.skillmd.com/api/skills/agentsorg/imagery/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: agentsorg (https://skillmd.com/u/agentsorg)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/agentsorg/imagery

---



# Pictures in the Interface

The default is no photograph. Product UI earns its texture from type, spacing, and material, and every picture you add is an aspect ratio to lock, a placeholder to design, two themes to survive, and bytes on the critical path. A picture belongs only when the picture *is* the content: something a user uploaded, a product being sold, a chart of their data, or a screenshot of the thing you actually built. Benji Taylor's site is the reference case — 53 device frames showing real product work, and zero stock photographs, no 3D render, no hero image, not even a photo of himself. When a picture does belong, it ships with its ratio locked before a single byte arrives.

The sibling to keep straight is `iconography`. An icon is a glyph drawn to a stroke grid and sized against the type it sits beside; a picture is a raster or a scene with its own intrinsic ratio and load cost. If the asset has a stroke weight, it is `iconography`. Two more edges: how long an image may take before its placeholder becomes a loading state is `ui-states`, and the dimming ratio a photograph takes in the dark theme is `dark-mode`.

**Work inside whatever image pipeline already exists.** Look for `next/image`, Astro's `<Image>`, `@nuxt/image`, an image CDN (Cloudinary, imgix, Vercel's optimizer), or a plain `<img>` convention. Hand-writing `srcset` beside a framework component that generates it produces two competing sources of truth and one of them will rot. If the project has no pipeline, say so explicitly before adding one — that is a dependency decision, not an image decision.

## Quick Reference

| You need | Open |
|---|---|
| Format choice, `srcset` and `sizes`, lazy versus priority, SVG hygiene, the placeholder ladder | `references/formats-and-loading.md` — open it before writing any `<img>` or `<picture>` markup. |
| Sizes, shapes, fallback chain, stacked groups, presence dots | `references/avatars.md` — open it the moment a person, team, or account is represented by a picture. |

## Core Principles

1. **Lock the ratio before the bytes arrive.** Set `aspect-ratio` on the box *and* `width`/`height` attributes on the `<img>`, even when CSS resizes it — the attributes are what let the browser reserve the space during parse. An image without reserved space is a layout shift with a delay fuse. The exception is genuinely unknown intrinsic dimensions at render time, which get a fixed-height container plus `object-fit: cover`.

2. **One ratio per collection.** A grid of `16:9`, `4:3`, and `3:2` cards reads as broken even though every cell is individually correct, because the eye tracks the gaps between cards, not the cards. Pick one ratio for the surface and crop into it. The exception is masonry, which is explicitly a mixed-ratio format and must therefore be visibly, deliberately masonry.

3. **Choose the placeholder by role, not by duration.** Hero and above-the-fold images get a blur-up inlined as a `data:` URI so it travels in the document itself; grid thumbnails get a single dominant colour; avatars get initials and never a spinner; decorative images get nothing at all. A spinner on an image is always wrong — it promises indeterminate work where the shape is already known. The exception is a batch arriving together, such as a paged-in gallery, which takes one skeleton for the group.

4. **Crop by art direction, not by CSS luck.** `object-fit: cover` on a hero will centre-crop a face out of the frame at narrow widths and nobody will notice until a customer does. Set `object-position` at minimum; use `<picture>` with a genuinely different crop when the subject leaves the frame. The exception is user-uploaded content, whose subject you cannot know — square-crop from centre and give the user a way to reposition it.

5. **Every image carries a `1px` inset outline: black at 10% in light, white at 10% in dark.** Photographs have their own edges, and without a boundary a light-topped image bleeds into a light page. Draw it with `outline: 1px solid rgb(0 0 0 / 0.1)` and `outline-offset: -1px` so it costs no layout space. Never a palette neutral (`slate`, `zinc`, `#111827`) and never the accent — a tinted line picks up the surface behind the image and reads as grime along the edge. The exception is a cut-out asset on transparency, where the outline would trace the bounding box instead of the shape.

6. **Screenshots get a second capture, not a filter.** Running a UI screenshot through a dark-mode brightness filter produces inverted-looking chrome and grey-on-grey text, and it announces that nobody looked. Capture the app in its dark theme and swap sources with `<picture>` and `prefers-color-scheme`. The exception is photographs, which have no dark variant to capture and take the dimming treatment `dark-mode` defines.

7. **Illustration obeys the icon system's physics.** Match illustration stroke weight to the icon set — `2px` illustration strokes beside `1px` icons read as two different products. Hold one scene to roughly three colours, all from the UI's tokens; recolour any purchased or community pack before it ships. Keep light direction consistent with the UI's shadow direction. Neither icons nor illustrations scale up: a `16px` mark blown to `48px` keeps its now-spindly stroke and must be redrawn. The exception is a licensed brand asset you are not permitted to alter.

8. **Show product work running, framed, and slowable.** A still of an interface proves layout; it proves nothing about behaviour, which is where the work actually is. Put the recording inside a device frame so the viewer knows what they are looking at, and ship a `1x / 0.5x` speed toggle — Benji Taylor puts one on every demo on his site, which turns a portfolio piece into something a reader can actually inspect. The exception is when layout genuinely is the subject; then a still is honest.

## Smell / Fix

| Smell | Fix |
|---|---|
| Page jumps as images load | No reserved space — add `aspect-ratio` plus `width`/`height` attributes |
| Card grid feels ragged though each card is fine | Mixed ratios — crop the collection to one |
| Spinner sitting in an image box | Blur-up, dominant colour, or nothing — never a spinner |
| Faces cropped out at mobile widths | `object-position`, or a second crop via `<picture>` |
| Image edges look dirty or smudged | Tinted outline — pure black/white at 10% only |
| Dark theme screenshot looks washed out | It is a filtered light capture; take a real dark capture |
| Stock photo of people at laptops | Delete it. Nothing replaces it |
| Illustration looks bolted on | Recolour to tokens, match stroke weight and light direction |
| Hero image is the LCP and lazy-loaded | Remove `loading="lazy"`, add `fetchpriority="high"` |

## Output Format

Every image ships this contract — no element leaves with a field missing:

```html
<img
  src="/hero-800.avif"
  srcset="/hero-800.avif 800w, /hero-1600.avif 1600w"
  sizes="(min-width: 768px) 50vw, 100vw"
  width="1600" height="900"
  alt="Order confirmation screen with the receipt expanded"
  decoding="async"
  fetchpriority="high"
  class="-outline-offset-1 outline-1 outline-black/10 dark:outline-white/10"
/>
```

Decorative images take `alt=""` — empty, never missing, and never a filename. Below the fold, drop `fetchpriority` and add `loading="lazy"`.

## Checklist

- [ ] Every image has reserved space before load (`aspect-ratio` + `width`/`height`)
- [ ] One aspect ratio per collection, or the layout is deliberately masonry
- [ ] Placeholder chosen by role; no spinners in image boxes
- [ ] Crops verified at the narrowest supported width, not just at desktop
- [ ] `1px` inset outline, black/white at 10%, on every non-cut-out image
- [ ] Dark theme has real dark captures for screenshots, not filtered light ones
- [ ] Illustrations match icon stroke weight, token colours, and light direction
- [ ] LCP image is not lazy-loaded and carries `fetchpriority="high"`
- [ ] Every image has an `alt` attribute, empty if decorative
- [ ] No stock photography, no generic 3D render, no blob-and-character illustration

