Placeholder Images That Do Not Cause Layout Shift
Design review on a laptop: cards look aligned. Lighthouse on a throttled profile: CLS 0.28. The villain is usually not a font — it is a hero image that reserved the wrong box, then swapped to a taller photo.
Reserve the final geometry early
Core Web Vitals treat unexpected layout movement as user pain. Images are frequent offenders because developers drop <img src="https://placehold.co/600x400"> while the design calls for 1200×800 (same ratio — fine) or 1200×500 (different ratio — reflow).
Checklist for every placeholder:
- Same aspect ratio as the intended asset
- Explicit width and height attributes (intrinsic ratio)
- CSS that constrains layout (
max-width: 100%; height: auto) - Final
srcswap without changing those dimensions
<img
src="/tools-generated/hero-1200x630.png"
width="1200"
height="630"
alt=""
decoding="async"
/>
Generate dummies at the real pixel size you will ship, not at “whatever the placeholder site defaulted to.”
Responsive grids and art direction
srcset and <picture> complicate placeholders. If art direction crops differently at mobile vs desktop, each breakpoint needs its own reserved ratio (or a shared box with object-fit that matches production). A single square placeholder in a slot that becomes 16:9 on desktop will jump.
For card grids, prefer identical ratios across cards so one late-loading image does not make the masonry dance. Uniform media boxes are boring and stable — good.
Dev convenience without prod leakage
A dummy image generator keeps placeholders on your terms: exact dimensions, labeled sizes for QA (“1200×630”), no random Unsplash content that accidentally ships. Wire Storybook and local fixtures to those URLs. CI can grep for known placeholder hostnames and fail the build.
Measuring whether you fixed it
Chrome DevTools Performance/Experience traces and field CLS (CrUX) tell you if users still see jumps. Lab Lighthouse on a cold cache with throttling is a useful gate in CI for template changes. If CLS spikes after a release that “only changed images,” compare intrinsic dimensions in the HTML diff before blaming third-party scripts.
Skeleton screens must match final geometry too. A skeleton that is shorter than the eventual card causes the same class of shift as a wrong placeholder PNG. Animate opacity inside a fixed box; do not grow the box when content arrives.
Priority and decoding
fetchpriority="high" on LCP images helps paint, not layout stability. decoding="async" avoids main-thread decode jank but still needs a reserved box. Do not remove width/height to “make responsive work” — use CSS for responsiveness while keeping the ratio attributes.
For background images in CSS, reserve space on the container explicitly (aspect-ratio or fixed height). Empty divs with background swaps are frequent CLS sources because there is no intrinsic image size to infer.
Lazy-loading below-the-fold images (loading="lazy") is compatible with reserved boxes; lazy-loading is not an excuse to omit dimensions. Above-the-fold LCP candidates usually should not be lazy. Prefer modern formats (AVIF/WebP) only after the layout box is correct — format swaps should not change width and height.
QA ritual
Label placeholders with dimensions in the image itself (“1200×630”) so a glance at the page reveals mismatches during design QA. When the real asset lands, the label disappears and the box should not move a pixel. Also verify Open Graph and social crop frames separately — those boxes are not always the same ratio as in-app cards.
Match the box. Then worry about compression, format, and blur-up. Order matters: geometry first, polish second.