CSS for Web Vitals

CSS-related techniques for optimizing Web Vitals

Katie Hempenius
Katie Hempenius

The way you write your styles and build layouts can have a major impact on Core Web Vitals. This is particularly true for Cumulative Layout Shift (CLS) and Largest Contentful Paint (LCP).

This article covers CSS-related techniques for optimizing Web Vitals. These optimizations are broken down by different aspects of a page: layout, images, fonts, animations, and loading. Along the way, we'll explore improving an example page:

Screenshot of example site

Inserting content into a page after the surrounding content has already loaded pushes everything else on the page down. This causes layout shifts.

Cookie notices, particularly those placed at the top of the page, are a common example of this problem. Other page elements that often cause this type of layout shift when they load include ads and embeds.

Identify

The Lighthouse "Avoid large layout shifts" audit identifies page elements that have shifted. For this demo, the results look like this:

Lighthouse's 'Avoid large layout shifts' audit

The cookie notice is not listed in these findings because the cookie notice itself isn't shifting when it loads. Rather, it causes the items below it on the page (that is, div.hero and article) to shift. For more information on identifying and fixing layout shifts, see Debugging Layout Shifts.

Fix

Place the cookie notice at the bottom of the page using absolute or fixed positioning.

Cookie notice displayed at bottom of page

Before:

.banner {
  position: sticky;
  top: 0;
}

After:

.banner {
  position: fixed;
  bottom: 0;
}

Another way to fix this layout shift would be to reserve space for the cookie notice at the top of the screen. This approach is equally effective. For more information, see Cookie notice best practices.

Images

Images and Largest Contentful Paint (LCP)

Images are commonly the Largest Contentful Paint (LCP) element on a page. Other page elements that can be the LCP element include text blocks and video poster images. The time at which the LCP element loads determines LCP.

It's important to note that a page's LCP element can vary from page load to page load depending on the content that is visible to the user when the page is first displayed. For example, in this demo, the background of the cookie notice, the hero image, and the article text are some of the potential LCP elements.

Diagram highlighting the page's LCP element in different scenarios.

In the example site, the background image of the cookie notice is actually a large image. To improve LCP, you could instead paint the gradient in CSS, rather than load an image to create the effect.

Fix

Change the .banner CSS to use a CSS gradient rather than an image:

Before:

background: url("https://cdn.pixabay.com/photo/2015/07/15/06/14/gradient-845701\_960\_720.jpg")

After:

background: linear-gradient(135deg, #fbc6ff 20%, #bdfff9 90%);

Images and layout shifts

Browsers can only determine the size of an image once the image loads. If the image load occurs after the page has been rendered, but no space has been reserved for the image, a layout shift occurs when the image appears. In the demo, the hero image is causing a layout shift when it loads.

Identify

To identify images without explicit width and height, use Lighthouse's "Image elements have explicit width and height" audit.

Lighthouse's 'Image elements have explicit width and height' audit

In this example, both the hero image and article image are missing width and height attributes.

Fix

Set the width and height attributes on these images to avoid layout shifts.

Before:

<img src="https://source.unsplash.com/random/2000x600" alt="image to load in">
<img src="https://source.unsplash.com/random/800x600" alt="image to load in">

After:

<img src="https://source.unsplash.com/random/2000x600" width="2000" height="600" alt="image to load in">
<img src="https://source.unsplash.com/random/800x600" width="800" height="600" alt="image to load in">
The image now loads without causing a layout shift.

Fonts

Fonts can delay text rendering and cause layout shifts. As a result, it is important to deliver fonts quickly.

Delayed text rendering

By default, a browser will not immediately render a text element if its associated web fonts have not loaded yet. This is done to prevent a "flash of unstyled text" (FOUT). In many situations, this delays First Contentful Paint (FCP). In some situations, this delays Largest Contentful Paint (LCP).

Layout shifts

Font swapping, while excellent for displaying content to the user quickly, has the potential to cause layout shifts. These layout shifts occur when a web font and its fallback font take up different amounts of space on the page. Using similarly proportioned fonts will minimize the size of these layout shifts.

Diagram showing a layout shift caused by a font swap
In this example, font swapping caused page elements to shift upwards by five pixels.

Identify

To see the fonts that are being loaded on a particular page, open the Network tab in DevTools and filter by Font. Fonts can be large files, so only using fewer fonts is generally better for performance.

Screenshot of a font displayed in DevTools

To see how long it takes for the font to be requested, click on the Timing tab. The sooner that a font is requested, the sooner it can be loaded and used.

Screenshot of 'Timing' tab in DevTools

To see the request chain for a font, click on the Initiator tab. Generally speaking, the shorter the request chain, the sooner the font can be requested.

Screenshot of 'Initiator' tab in DevTools

Fix

This demo uses the Google Fonts API. Google Fonts provides the option to load fonts via <link> tags or an @import statement. The <link> code snippet includes a preconnect resource hint. This should result in faster stylesheet delivery than using the @import version.

At a very high-level, you can think of resource hints as a way to hint to the browser that it will need to set up a particular connection or download a particular resource. As a result, the browser will prioritize these actions. When using resource hints, keep in mind that prioritizing a particular action takes away browser resources from other actions. Thus, resource hints should be used thoughtfully and not for everything. For more information, see Establish network connections early to improve perceived page speed.

Remove the following @import statement from your stylesheet:

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400&family=Roboto:wght@300&display=swap');

Add the following <link> tags to the <head> of the document:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap" rel="stylesheet">

These link tags instruct the browser to establish an early connection to the origins used by Google Fonts and to load the stylesheet that contains the font declaration for Montserrat and Roboto. These <link> tags should be placed as early in the <head> as possible.

Animations

The primary way that animations affect Web Vitals is when they cause layout shifts. There are two types of animations that you should avoid using: animations that trigger layout and "animation-like" effects that move page elements. Typically these animations can be replaced with more performant equivalents by using CSS properties like transform, opacity, and filter. For more information, see How to create high-performance CSS animations.

Identify

The Lighthouse "Avoid non-composited animations" audit may be helpful for identifying non-performant animations.

Lighthouse's 'Avoid non-composited animations' audit

Fix

Change the slideIn animation sequence to use transform: translateX() rather than transitioning themargin-left property.

Before:

.header {
  animation: slideIn 1s 1 ease;
}

@keyframes slideIn {
  from {
    margin-left: -100%;
  }
  to {
    margin-left: 0;
  }
}

After:

.header {
  animation: slideIn 1s 1 ease;
}

@keyframes slideIn {
  from {
    transform: translateX(-100%);
  }

  to {
    transform: translateX(0);
  }
}

Critical CSS

Stylesheets are render-blocking. This means that the browser encounters a stylesheet, it will stop downloading other resources until the browser has downloaded and parsed the stylesheet. This may delay LCP. To improve performance, consider removing unused CSS, inlining critical CSS, and deferring non-critical CSS.

Conclusion

Although there is still room for further improvements (for example, using image compression to deliver images more quickly), these changes have significantly improved the Web Vitals of this site. If this were a real site, the next step would be to collect performance data from real users to assess whether it is meeting the Web Vitals thresholds for most users. For more information about Web Vitals, see Learn Web Vitals.