View contents
Image Dimensions Implementation Guide: Code, Automation, and Complex Cases
Introduction
This guide covers how to declare image dimensions consistently across HTML, CSS, and modern frameworks, how to automate dimension extraction from a CMS, and how to handle responsive layouts without sacrificing the space reservation that prevents Cumulative Layout Shift (CLS). The UXR SEO Analyzer reports every <img> without declared width/height as a Core Web Vitals finding.
If you need the conceptual background first, see Image Dimensions Explained.
Base Pattern: HTML Attributes
<img
src="/img/product.jpg"
alt="Gray running shoes with orange sole"
width="800"
height="600"
loading="lazy">
The browser uses width and height to compute the intrinsic aspect ratio and reserves that space even if CSS later scales the image to a different container width—as long as the CSS preserves the proportion.
Responsive Pattern: CSS That Preserves Proportion
img {
max-width: 100%;
height: auto;
}
This classic rule makes the image fit the container's width while the browser calculates height automatically from the HTML's width/height attributes—preserving the space reservation without fixing a rigid pixel size.
Using CSS aspect-ratio Directly
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}
aspect-ratio is especially useful when the image is a CSS-generated background or when the container's size doesn't match the file's original dimensions.
Automation in Frameworks
Next.js
The next/image component requires width/height (or fill with a sized container) and automatically computes the aspect-ratio placeholder, failing the build if they're missing.
Nuxt / Vue
@nuxt/image offers the same validation through the <NuxtImg> component, generating a correctly sized placeholder during SSR.
Headless CMS (Contentful, Sanity, Strapi)
Most headless CMSs return the asset's original dimensions in the API response. Extract and persist those values alongside the URL at query time, instead of assuming a fixed size.
const { url, width, height } = image.fields.file.details.image;
// <img src={url} width={width} height={height} alt="..." />
Validation and Testing
- Run Lighthouse and check the CLS section of the Core Web Vitals report.
- Use Chrome DevTools (Performance panel, "Experience" section) to visualize elements causing layout shift.
- Add a lint rule (for example,
eslint-plugin-jsx-a11ywith image rules) that fails the build whenwidth/heightis missing.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
Using width: 100%; height: 100% in CSS without aspect-ratio | The browser can't compute height and collapses the space | Add aspect-ratio or keep height: auto |
| Declaring incorrect dimensions (different from the real file) | The computed aspect ratio mismatches, distorting or cropping the image | Sync dimensions with the actual file on every upload |
| Omitting dimensions on JavaScript-injected images | Layout shift happens after hydration, invisible in static tests | Reserve the container with CSS before injecting the image |
Complex Case: Galleries and Carousels
Gallery components typically swap the visible image via JavaScript when navigating between slides. If the new <img> element doesn't include width/height or the container doesn't have a fixed CSS aspect-ratio, every slide change can introduce an additional layout shift, even if the page's initial load was well optimized.
.carousel-slide {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.carousel-slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
By fixing the aspect-ratio on the slide's container rather than on the individual image, you guarantee the reserved space doesn't change no matter which image loads inside it.
Complex Case: Art-Directed Images With Dynamic Cropping
When you use <picture> with different crops per breakpoint (art direction), each <source> can have a different aspect ratio than the fallback image. In that case, declare width/height on the final <img> element based on the most likely crop (usually the desktop one), and use CSS aspect-ratio per breakpoint for cases where the crop changes significantly:
<picture>
<source media="(max-width: 600px)" srcset="/hero-mobile.jpg">
<source media="(min-width: 601px)" srcset="/hero-desktop.jpg">
<img src="/hero-desktop.jpg" width="1200" height="600" alt="Image description">
</picture>
@media (max-width: 600px) {
picture img { aspect-ratio: 4 / 5; }
}
Measuring the Real Impact in Production
Beyond Lighthouse (which measures under lab conditions), it's worth monitoring CLS with field data using the PerformanceObserver API with the layout-shift type, or by integrating a library like Google's web-vitals, to confirm real users aren't experiencing shifts the lab environment didn't capture (for example, on connections slower than simulated ones). Field data also surfaces device-specific issues lab testing tends to miss, such as older phones taking noticeably longer to decode large images, which widens the window during which an unreserved image space can shift.
import { onCLS } from 'web-vitals';
onCLS((metric) => {
console.log('This user\'s real CLS:', metric.value);
// send to your analytics system
});
Implementation Checklist
- Every image in the initial markup has
width/heightor an equivalentaspect-ratio. - Components that dynamically swap images (carousels, lightboxes) preserve the container's space reservation.
- Responsive CSS rules (
max-width: 100%; height: auto) don't break the computed proportion. - Field CLS (not just lab CLS) is monitored with
web-vitalsor an equivalent tool.
Complex Case: User-Generated Images With Unknown Dimensions
On platforms where users upload their own images (marketplaces, social networks, forums), you don't always know the file's dimensions ahead of time. The most robust solution is to read the actual dimensions at upload time—either server-side with a library like sharp (Node.js) or Pillow (Python)—and persist them alongside the file reference, instead of trying to guess them or using a fixed value that will likely distort some images.
import sharp from 'sharp';
async function getImageDimensions(filePath) {
const metadata = await sharp(filePath).metadata();
return { width: metadata.width, height: metadata.height };
}
// Store { width, height } in the database alongside the file record
Dominant Color Placeholder While Loading
Reserving the correct space solves the layout shift, but you can further improve perceived experience by showing a background color or a blurred placeholder (a technique known as LQIP, Low Quality Image Placeholder) while the high-resolution image finishes loading inside the already-reserved space:
.image-container {
aspect-ratio: 16 / 9;
background-color: #e2e2e2; /* approximate dominant color */
}
This technique doesn't affect CLS (the space was already reserved) but it reduces the "blank screen" feeling during load.
Auditing an Entire Site for Missing Dimensions
For a large existing site, checking every template manually isn't practical. A lightweight crawler script can flag every rendered <img> missing both the width/height attributes and any CSS aspect-ratio rule targeting it, then group the results by template or component so engineering can fix the shared source once instead of patching individual pages. Pair this audit with the CLS section of a site-wide Lighthouse CI run so regressions get caught in pull requests rather than after deployment.
Related Articles
- Image Dimensions Explained - Why it matters
- CLS Optimization Guide - Full layout-shift strategy
- Responsive Images Implementation Guide - srcset and sizes
References
- web.dev - Optimize Cumulative Layout Shift
- web.dev - Cumulative Layout Shift (CLS)
- Chrome Developers - Layout shift culprits
- Chrome Developers - Chrome UX Report metrics
