Back to Blog
PerformanceJuly 4, 20264 min read

CLS Optimization: Preventing Layout Shifts with Skeletons and Aspect-Ratio

Master CLS optimization to stop UI jitter. Learn how to implement stable image placeholders and skeleton screens to keep your layout rock-solid.

CLSWeb PerformanceCSSFrontendCore Web VitalsUXPerformanceWeb Vitals

During a recent refactor of a client’s product listing page, I watched the layout jump by nearly 200 pixels every time the featured images loaded. It was a classic case of browser layout thrashing, and while I’d previously covered Eliminating Cumulative Layout Shift: A Practical Guide to CLS Optimization, this specific implementation required something more robust than simple fixed-height containers.

If you’re struggling with CLS optimization, you know the frustration: the user starts reading, the hero image pops in, and the text gets shoved down the page. It’s a jarring experience that kills your Core Web Vitals score.

The Problem with Dynamic Content

When you fetch data from an API, your browser doesn't know the dimensions of the incoming content. If you inject a <img> tag without explicit width and height attributes, the browser reserves zero space for it initially. Once the image metadata arrives, the browser recalculates the layout, causing that annoying shift.

We first tried hard-coding heights using CSS, but that broke our responsive grid. If the width changed, the aspect ratio was lost, and the images looked stretched or cropped. We needed a fluid approach that respected the design constraints without sacrificing performance.

Implementing Stable Image Placeholders

The most effective way to handle this is by using the CSS aspect-ratio property. It’s supported in all modern browsers and is a game-changer for layout shift prevention.

Instead of letting the browser guess, you define the container's footprint before the image arrives:

CSS
#9CDCFE">color:#4EC9B0">.image-container {
  #9CDCFE">aspect-ratio: 16 / 9;
  #9CDCFE">width: 100%;
  #9CDCFE">background: #f0f0f0;
  #9CDCFE">display: block;
}

#9CDCFE">color:#4EC9B0">img {
  #9CDCFE">width: 100%;
  #9CDCFE">height: auto;
  #9CDCFE">display: block;
}

By adding aspect-ratio: 16 / 9 to the parent, the browser calculates the height based on the width immediately. Even if the image takes 300ms to fetch, the space is already reserved. As I detailed in Cumulative Layout Shift: Fixing Dynamic Media with CSS, this simple CSS rule is often enough to drop your CLS score significantly.

Beyond Images: Using Skeleton Screens

Sometimes, images aren't the only culprit. Text blocks, ads, and dynamic widgets often cause similar issues. This is where skeleton screens shine.

A skeleton screen is essentially a low-fidelity version of your UI that mimics the final layout. It signals to the user that content is coming without triggering a massive layout shift when the data finally renders.

Designing a Simple Skeleton

When building these, avoid animations that trigger heavy repaints (like box-shadow or border changes). Stick to opacity or simple background color transitions.

StrategyBest ForImplementation
Aspect-RatioImages, Videosaspect-ratio CSS property
Skeleton ScreensText, Lists, WidgetsCSS pseudo-elements (::after)
CSS ContainmentComplex componentscontain: layout size

If you’re still seeing shifts, you might want to look into Cumulative Layout Shift Optimization: Mastering CSS Containment to isolate parts of your DOM from the rest of the page flow.

Putting It Into Practice

When I implemented these changes, I noticed our CLS score dropped from 0.25 to roughly 0.04 over about two days of testing. The key wasn't just fixing the images; it was ensuring that every dynamic element had a reserved "seat" at the table.

HTML
<!-- Example of a stable skeleton layout -->
style="color:#808080"><style="color:#4EC9B0">div class="card">
  style="color:#808080"><style="color:#4EC9B0">div class="skeleton-image">style="color:#808080"></style="color:#4EC9B0">div>
  style="color:#808080"><style="color:#4EC9B0">div class="skeleton-text">style="color:#808080"></style="color:#4EC9B0">div>
style="color:#808080"></style="color:#4EC9B0">div>
CSS
#9CDCFE">color:#4EC9B0">.skeleton-image {
  #9CDCFE">aspect-ratio: 1 / 1;
  #9CDCFE">background: #e0e0e0;
  #9CDCFE">border-radius: 4px;
}

#9CDCFE">color:#4EC9B0">.skeleton-text {
  #9CDCFE">height: 20px;
  #9CDCFE">width: 80%;
  #9CDCFE">margin-top: 10px;
  #9CDCFE">background: #e0e0e0;
}

Frequently Asked Questions

Does aspect-ratio work on all images? It works on any element, but it’s most effective on img, video, and iframe elements. Just ensure your width and height attributes are set on the <img> tag as well, as browsers use these to calculate the aspect ratio before the CSS even loads.

Do skeleton screens hurt performance? They shouldn't. If you keep your CSS simple—using background colors and basic shapes—the overhead is negligible. Avoid complex SVG animations or heavy JavaScript-based observers if you’re concerned about the main thread.

What if I don't know the aspect ratio of the content? This is the hardest scenario. You can either use a fixed-height container and object-fit: cover to force a standard size, or use a ResizeObserver to monitor the container height, though that is much more complex to implement correctly.

I’m still experimenting with whether content-visibility: auto provides a better trade-off for very long pages, but for now, the combination of explicit aspect ratios and skeleton screens is the most reliable path to a stable layout. Don't over-engineer it—start with the CSS basics and watch your metrics settle down.

Similar Posts