imagery slop

Images fail in familiar ways: a meaningless placeholder shipped to production, a jagged clip-path chewing the edge of a hero, text sitting on a darkened photo, a broken image icon where a caption should be, stock art that decorates instead of informing. Each section puts the failing treatment (A) next to a working one (B) with the same content, live, and states the rule that settles it. The demo photos are gradient stand-ins so every demo renders offline. Pattern source: impeccable.style/slop; the reasoning, demos and fixes are ours.

placeholder illustrations

A real feature ships with the mock's placeholder art: a gradient blob, a squiggle, a grey "image goes here" box. The reader reads it, finds no information, and learns that surfaces on this product lie. An illustration is content: it either shows real data, the actual object, or the actual screen, and otherwise it is removed and the space given to information.

A: placeholder art

usage insights

your team ran 1,204 queries this week.

the blob and squiggle say nothing about usage; they were placeholders in the mock and now they are the interface.

B: the illustration is the data

usage insights

your team ran 1,204 queries this week.

same content. the slot shows the real weekly bars; the number and the picture agree.

ruling
Ship the mock's art only when the art is true: it shows the real object, real data, or the real screen. A placeholder that reached production is a TODO the reader can see.
<div className="flex flex-col gap-1 rounded-lg border border-border bg-card">
<div className="flex h-28 items-end gap-2 px-8 pb-4 pt-6">
{weeks.map((w) => (
<div key={w} className="flex-1 rounded-sm bg-emerald-600/70" style={{ height: `${w.count}%` }} />
))}
</div>
<div className="flex flex-col gap-1 p-4">
<h4 className="text-sm">usage insights</h4>
<p className="text-sm text-zinc-400">your team ran 1,204 queries this week.</p>
</div>
</div>

jagged masks

A hero band clips its bottom edge with a zigzag clip-path so the block "feels dynamic". The teeth carry no meaning, they truncate the band's fill in arbitrary places, and they make the layout grid unknowable: the next section cannot align to an edge that jumps 12 pixels between two teeth. A section edge is either straight or a real shape the content defines; a saw blade is neither.

A: zigzag clip-path

ship notes

the teeth chop the photo stand-in at random depths; the section below cannot reference the edge.

B: straight edge, one rule

ship notes

same content. the band ends at a line the next section can align to; hierarchy moves to the title.

ruling
Cut a container's edge only when the content demands it (a photo of a mountain, a diagonal flow). A decoration the next section has to work around is damage, not style.
<section className="flex h-32 items-center justify-center bg-gradient-to-br from-sky-700 to-emerald-700">
<h4 className="text-lg text-white">ship notes</h4>
</section>
{/* the next section aligns to a straight edge; no clip-path, no mask */}

image under overlay

Text sits on a photo that a dark overlay dims just enough. The overlay is a symptom: the text needed a fill with a known contrast, so a scrim was painted under it. Now the photo reads muddy, the text contrast changes wherever the photo is bright, and every future sentence on that surface inherits the same wager. Text goes on flat fills; photos stand beside text, not under it.

A: text on a dimmed photo

q3 field report

response time fell 44% after the cache rewrite.

the white headline survives the scrim, the body sits near the floor; brighten any pixel of the photo and both drift.

B: photo beside text on a flat fill

q3 field report

response time fell 44% after the cache rewrite.

same content. the photo keeps full contrast; the text sits on a card with one known contrast ratio.

ruling
Put text on a fill you chose, or put it beside the photo. If a sentence can only be read because a scrim darkened its background, the layout is asking the overlay to do contrast's job.
<div className="grid grid-cols-[7rem_1fr] rounded-lg border border-border bg-card">
<img src="/report-cover.webp" alt="q3 cover" className="h-full w-full object-cover" />
<div className="flex flex-col justify-center gap-1 p-4">
<h4 className="text-sm">q3 field report</h4>
<p className="text-sm text-zinc-400">response time fell 44% after the cache rewrite.</p>
</div>
</div>

broken image

An image URL rots or a deploy drops the asset, and the reader gets the browser's broken-image glyph, or worse, an empty box, because the tag shipped with no alt and no plan. The slot was allocated, the layout still pays for it, and the failure is silent. An image slot is a contract: the bytes arrive, or a labeled fallback takes the same box.

A: no alt, no fallback

sunset over the lake

shot at golden hour from the east pier.

this img 404s live: the glyph or an empty band appears, and a screen reader reads nothing.

B: alt text plus a labeled fallback

sunset over the lake from the east pier

sunset over the lake

shot at golden hour from the east pier.

same content. while the bytes exist the photo shows; if they never arrive, a labeled panel takes the exact same slot.

ruling
Every image carries alt text that describes it and a fallback that owns its box when the request fails. An empty box where an image was promised is a bug in the page, not in the network.
function SafeImage({ src, alt }: { src: string; alt: string }) {
const [failed, setFailed] = useState(false)
// the ref check catches a failure that happened before hydration,
// when the onError listener was not attached yet
const check = (node: HTMLImageElement | null) => {
if (node && node.complete && node.naturalWidth === 0) setFailed(true)
}
if (failed) {
return (
<div role="img" aria-label={alt} className="flex h-28 items-center justify-center bg-muted text-xs">
photo unavailable: {alt}
</div>
)
}
return <img ref={check} src={src} alt={alt} onError={() => setFailed(true)} className="h-28 w-full object-cover" />
}

decorative stock art

A settings panel gains a lightbulb photo because the page felt heavy. The image is unrelated to the content, so the reader must first decide it is noise, every time. Stock art also lies about specificity: the same handshake exists on ten thousand pages, which tells the reader this page invested in none of its own. Space and type already separate content; an image earns its slot only by showing this product's real subject.

A: stock lightbulb beside settings

storage settings

keep 30 days of snapshots. older ones roll up hourly.

the illustration connects to storage in no way; half the card is spent on noise.

B: the slot goes to information

storage settings

keep 30 days of snapshots. older ones roll up hourly.

current usage: 41.2 gb across 6 buckets.

same content. without the art, the panel states the real numbers the reader came for.

ruling
An image on a functional surface must identify, demonstrate, or measure something real on that surface. If removing it changes nothing the reader needs, it was occupying attention the content paid for.
<div className="flex flex-col gap-1 rounded-lg border border-border bg-card p-4">
<h4 className="text-sm">storage settings</h4>
<p className="text-sm text-zinc-400">keep 30 days of snapshots. older ones roll up hourly.</p>
<p className="text-sm text-zinc-400">current usage: 41.2 gb across 6 buckets.</p>
</div>
{/* no art slot: the space reports usage instead */}

search pages

go to any page