Debug layout shifts
Learn how to identify and fix layout shifts.
The first part of this article discusses tooling for debugging layout shifts, while the second part discusses the thought process to use when identifying the cause of a layout shift.
Tooling #
Layout Instability API #
The Layout Instability API is the browser mechanism for measuring and reporting layout shifts. All tools for debugging layout shifts, including DevTools, are ultimately built upon the Layout Instability API. However, using the Layout Instability API directly is a powerful debugging tool due to its flexibility.
Usage #
The same code snippet that measures Cumulative Layout Shift (CLS) can also serve to debug layout shifts. The snippet below logs information about layout shifts to the console. Inspecting this log will provide you information about when, where, and how a layout shift occurred.
let cls = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
console.log('Current CLS value:', cls, entry);
}
}
}).observe({type: 'layout-shift', buffered: true});
When running this script be aware that:
- The
buffered: true
option indicates that thePerformanceObserver
should check the browser's performance entry buffer for performance entries that were created before the observer's initialization. As a result, thePerformanceObserver
will report layout shifts that happened both before and after it was initialized. Keep this in mind when inspecting the console logs. An initial glut of layout shifts can reflect a reporting backlog, rather than the sudden occurrence of numerous layout shifts. - To avoid impacting performance, the
PerformanceObserver
waits until the main thread is idle to report on layout shifts. As a result, depending on how busy the main thread is, there may be a slight delay between when a layout shift occurs and when it is logged in the console. - This script ignores layout shifts that occurred within 500 ms of user input and therefore do not count towards CLS.
Information about layout shifts is reported using a combination of two APIs: the LayoutShift
and LayoutShiftAttribution
interfaces. Each of these interfaces are explained in more detail in the following sections.
LayoutShift #
Each layout shift is reported using the LayoutShift
interface. The contents of an entry look like this:
duration: 0
entryType: "layout-shift"
hadRecentInput: false
lastInputTime: 0
name: ""
sources: (3) [LayoutShiftAttribution, LayoutShiftAttribution, LayoutShiftAttribution]
startTime: 11317.934999999125
value: 0.17508567530168798
The entry above indicates a layout shift during which three DOM elements changed position. The layout shift score of this particular layout shift was 0.175
.
These are the properties of a LayoutShift
instance that are most relevant to debugging layout shifts:
LayoutShiftAttribution #
The LayoutShiftAttribution
interface describes a single shift of a single DOM element. If multiple elements shift during a layout shift, the sources
property contains multiple entries.
For example, the JSON below corresponds to a layout shift with one source: the downward shift of the <div id='banner'>
DOM element from y: 76
to y:246
.
// ...
"sources": [
{
"node": "div#banner",
"previousRect": {
"x": 311,
"y": 76,
"width": 4,
"height": 18,
"top": 76,
"right": 315,
"bottom": 94,
"left": 311
},
"currentRect": {
"x": 311,
"y": 246,
"width": 4,
"height": 18,
"top": 246,
"right": 315,
"bottom": 264,
"left": 311
}
}
]
The node
property identifies the HTML element that shifted. Hovering on this property in DevTools highlights the corresponding page element.
The previousRect
and currentRect
properties report the size and position of the node.
- The
x
andy
coordinates report the x-coordinate and y-coordinate respectively of the top-left corner of the element - The
width
andheight
properties report the width and height respectively of the element. - The
top
,right
,bottom
, andleft
properties report the x or y coordinate values corresponding to the given edge of the element. In other words, the value oftop
is equal toy
; the value ofbottom
is equal toy+height
.
If all properties of previousRect
are set to 0 this means that the element has shifted into view. If all properties of currentRect
are set to 0 this means that the element has shifted out of view.
One of the most important things to understand when interpreting these outputs is that elements listed as sources are the elements that shifted during the layout shift. However, it's possible that these elements are only indirectly related to the "root cause" of layout instability. Here are a few examples.
Example #1
This layout shift would be reported with one source: element B. However, the root cause of this layout shift is the change in size of element A.

Example #2
The layout shift in this example would be reported with two sources: element A and element B. The root cause of this layout shift is the change in position of element A.

Example #3
The layout shift in this example would be reported with one source: element B. Changing the position of element B resulted in this layout shift.

Example #4
Although element B changes size, there is no layout shift in this example.

Check out a demo of how DOM changes are reported by the Layout Instability API.
DevTools #
Performance panel #
The Experience pane of the DevTools Performance panel displays all layout shifts that occur during a given performance trace—even if they occur within 500 ms of a user interaction and therefore don't count towards CLS. Hovering over a particular layout shift in the Experience panel highlights the affected DOM element.

To view more information about the layout shift, click on the layout shift, then open the Summary drawer. Changes to the element's dimensions are listed using the format [width, height]
; changes to the element's position are listed using the format [x,y]
. The Had recent input property indicates whether a layout shift occurred within 500 ms of a user interaction.

For information on the duration of a layout shift, open the Event Log tab. The duration of a layout shift can also be approximated by looking in the Experience pane for the length of the red layout shift rectangle.

For more information on using the Performance panel, refer to Performance Analysis Reference.
Highlight layout shift regions #
Highlighting layout shift regions can be a helpful technique for getting a quick, at-a-glance feel for the location and timing of the layout shifts occurring on a page.
To enable Layout Shift Regions in DevTools, go to Settings > More Tools > Rendering > Layout Shift Regions then refresh the page that you wish to debug. Areas of layout shift will be briefly highlighted in purple.
Thought process for identifying the cause of layout shifts #
You can use the steps below to identify the cause of layout shifts regardless of when or how the layout shift occurs. These steps can be supplemented with running Lighthouse—however, keep in mind that Lighthouse can only identify layout shifts that occurred during the initial page load. In addition, Lighthouse also can only provide suggestions for some causes of layout shifts—for example, image elements that do not have explicit width and height.
Identifying the cause of a layout shift #
Layout shifts can be caused by the following events:
- Changes to the position of a DOM element
- Changes to the dimensions of a DOM element
- Insertion or removal of a DOM element
- Animations that trigger layout
In particular, the DOM element immediately preceding the shifted element is the element most likely to be involved in "causing" layout shift. Thus, when investigating why a layout shift occurred consider:
- Did the position or dimensions of the preceding element change?
- Was a DOM element inserted or removed before the shifted element?
- Was the position of the shifted element explicitly changed?
If the preceding element did not cause the layout shift, continue your search by considering other preceding and nearby elements.
In addition, the direction and distance of a layout shift can provide hints about root cause. For example, a large downward shift often indicates the insertion of a DOM element, whereas a 1 px or 2 px layout shift often indicates the application of conflicting CSS styles or the loading and application of a web font.

These are some of the specific behaviors that most frequently cause layout shift events:
Changes to the position of an element (that aren't due to the movement of another element) #
This type of change is often a result of:
- Stylesheets that are loaded late or overwrite previously declared styles.
- Animation and transition effects.
Changes to the dimensions of an element #
This type of change is often a result of:
- Stylesheets that are loaded late or overwrite previously declared styles.
- Images and iframes without
width
andheight
attributes that load after their "slot" has been rendered. - Text blocks without
width
orheight
attributes that swap fonts after the text has been rendered.
The insertion or removal of DOM elements #
This is often the result of:
- Insertion of ads and other third-party embeds.
- Insertion of banners, alerts, and modals.
- Infinite scroll and other UX patterns that load additional content above existing content.
Animations that trigger layout #
Some animation effects can trigger layout. A common example of this is when DOM elements are 'animated' by incrementing properties like top
or left
rather than using CSS's transform
property. Read How to create high-performance CSS animations for more information.
Reproducing layout shifts #
You can't fix layout shifts that you can't reproduce. One of the simplest, yet most effective things you can do to get a better sense of your site's layout stability is take 5-10 minutes to interact with your site with the goal triggering layout shifts. Keep the console open while doing this and use the Layout Instability API to report on layout shifts.
For hard to locate layout shifts, consider repeating this exercise with different devices and connection speeds. In particular, using a slower connection speed can make it easier to identify layout shifts. In addition, you can use a debugger
statement to make it easier to step through layout shifts.
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
debugger;
console.log('Current CLS value:', cls, entry);
}
}
}).observe({type: 'layout-shift', buffered: true});
Lastly, for layout issues that aren't reproducible in development, consider using the Layout Instability API in conjunction with your front-end logging tool of choice to collect more information on these issues. Check out the example code for how to track the largest shifted element on a page.