Cumulative Layout Shift (CLS) is a Core Web Vital that measures how much a page unexpectedly moves around, causing a bad user experience. One of the best ways to fix this is to ensure the browser knows the page’s layout from the start.
CSS is a great way to tell the browser in advance how much space something will need when it is later loaded. Like an image or a dynamically loaded widget.
Page Lightning lets you define little chunks of CSS to do that. Just work out a CSS selector to identify what you want to size, then add appropriate CSS to define how it should be sized.

References: Optimize Cumulative Layout Shift.
Examples
Define the Aspect Ratio of Images

In some cases, the theme or contents do not define how tall an image is. This can mean the page expands once the image is loaded and its size is known. Maybe the logo in the top banner is causing the banner to grow larger because it’s oversized.
The image may be displayed in different sizes, e.g., smaller on mobile, so the best approach is to define the image’s aspect ratio (width/height) and let the browser work out its height. For example, the Cornerstone theme applies the class ‘header-logo-image’ to the logo, so a wide logo could be pre-sized with this:
.header-logo-image {
aspect-ratio: auto 16 / 9;
}
auto is used to ensure the image recalculates its aspect ratio once the image is loaded and its true aspect ratio is known.
I’d avoid using CSS width and height values, as they could override the theme’s image styling. e.g., it may be a fixed-width element with height determined by the aspect ratio (auto).
Sometimes aspect-ratio alone does not work; you may need to play with other properties to achieve the desired effect.
Review Stars
Product pages often show review stars at the top of their description. Sometimes these appear dynamically and push the entire description down, causing a layout shift. Defining their height can stop that.
Note that a widget’s height can sometimes vary with device width. Here’s an example that fixes a reviews.io star widget we encountered.
.productView-reviews {
height: 20px;
}
@media (min-width: 990px) {
.productView-reviews {
height: 24px;
}
}
Widget Heights

I’d recommend using some of Bobspadger’s widgets instead of the built-in carousel and image widget, as they are designed to be fast, responsive, and stable in height.
Some page builder or third-party widgets are slow to load and do not always define their height. This can cause a major shift when the widget is finally shown.
Unfortunately, determining a CSS selector for Page Builder widgets can be a bit tricky. If you inspect the widget in the browser, you should see a data-widget-id attribute that identifies the element the widget is in. Its value can be used to set the widget’s height. e.g. if the widget with id “123456789-1234-1234-1234-123456789” is always 530px tall:
div[data-widget-id="123456789-1234-1234-1234-123456789"] {
min-height: 530px;
}
If the widget’s height may vary. e.g. a product options section where different products have different options. Either go with the minimum height it will be, or define multiple style blocks limited to the pages they match.
Here’s an example we once used to fix a PayPal banner widget from causing a big shift:
div[data-pp-style-ratio='8x1'] {
aspect-ratio: 8 / 1;
}
Scroll Bar

Sometimes a page loads in a way that initially shows less content, then expands, causing the scroll bar to appear and the page to become slightly narrower, shifting things around.
This CSS ensures that the scroll bar is present from the start:
body {
overflow: scroll;
}
Search Providers
Third-party search providers dynamically load the content of pages. Because it is dynamic, an empty page may be seen before the content is added. If this happens, the footer may be visible for a short while before being pushed out of view. If this happens, adding a min-height to the content can prevent the big shift. Say your header is 200px high, this should put the footer just out of initial view:
.page-content {
min-height: calc(100vh - 200px);
}
View Transitions
This is a bit of a fun one. At times Page Lightning can make the next page appear so quickly that the user does not pick up that they are already on a new page. View transitions lets you add an animation between the pages, to indicate they have transitioned to a new page. Here’s a basic one that has the new page scroll up into view, over the old page. I’m sure a proper dev could make something cooler.
@view-transition {
navigation: auto;
}
::view-transition-new(root) {
animation: slide-up 300ms ease-out, fade-in 300ms ease-out;
}
::view-transition-group(root),
::view-transition-image-pair(root) {
animation: none;
}
::view-transition-old(root) {
animation: none;
opacity: 0;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slide-up {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}