Layout stability, images and fonts
A late arrival moves everything below it, unless its space was held before it came.
Predict first
Before you read: predict
ExamHall's start page shows the logo, an "Exam rules" heading, the rules paragraph and the Start exam button. The logo <img> has width="160" height="40". The rules are set in the web font "Exam Serif" with font-display: swap, and Arial stands behind it in font-family. On a slow connection the logo file, and then the font file, arrive after the page's first paint.
From first paint on, what does a layout-shift PerformanceObserver record?
The 30-second version
A layout shift is visible content changing position between frames, usually because something arrived late: an image or embed with no reserved size, content inserted above, or a web font that reflows its text. Reserve the space before it arrives, and give the fallback font the web font's metrics. Good CLS is 0.1 or less.
A late arrival moves everything below it, unless its space was held before it came.
How it works
Step 1 of 12
layout-shift entry with a score.Analogy: A class photo on tiered benches (school events)
A photographer lines up a class on benches while the parents watch the camera's live preview, the whole time.
| At the photo | In the page |
|---|---|
| a late child squeezed into a row shoves the children beside and behind her | content inserted above the reader (step 8) |
| the parents notice who moved, not who arrived | sources lists the moved elements (step 2) |
| a tape mark on the bench for each child on the list, there or not | width and height, aspect-ratio, min-height (step 6) |
| a child on her mark in a big winter coat still pushes the row | a ratio box grows when its content is taller (step 7) |
| a stand-in of the soloist's height and width holds her place, so she slips in unnoticed | a metric-matched fallback face (step 11) |
| if the soloist is not there when the photographer is ready, the stand-in stays in the photo | font-display: optional (step 10) |
| children swapping places because the photographer just asked spoil nothing | hadRecentInput (step 3) |
In real terms: what moves is what was already on screen; the fix is always to give the late arrival its space, or its exact stand-in, before it comes.
Breaks down when: the photographer waits until everyone is still before pressing the shutter. A loading page is watched at every frame, so each move counts when it happens.
Examples, run for real
4.1 ExamHall EXH-3380: the rules reflow under Start exam
ExamHall's start page set the rules in Exam Serif with font-display: swap, Arial behind it. On slow connections the font arrived after first paint and candidates reported the Start exam button jumping as they reached for it (EXH-3380). MR !3680 kept swap and added a fallback face on Arial whose numbers come from the two fonts:
@font-face {
font-family: "Exam Serif Fallback";
src: local("Arial");
size-adjust: 106.12%;
ascent-override: 92.72%;
descent-override: 25.72%;
line-gap-override: 0%;
}
p#rules { font-family: "Exam Serif", "Exam Serif Fallback", sans-serif; }
derivation: Exam Serif at 100 px: ascent 98.4 px, descent 27.3 px, line box 125.7 px (so line gap 0.0) · the rules sentence on one line: 7858.0 px in Exam Serif, 7404.6 px in Arial
derivation: size-adjust 106.12% · ascent-override 98.4 / 1.0612 = 92.72% · descent-override 27.3 / 1.0612 = 25.72% · line-gap-override 0%
EXH-3380 as found (swap, Arial behind it): shifts before the font 0 · when it arrives: 1 shift naming #rules, #start · Start exam moved down 36 px · rules in Exam Serif at the end: yes
MR !3680 with the fallback face (swap kept): shifts before the font 0 · when it arrives: 0 shifts · Start exam moved 0 px · rules in Exam Serif at the end: yes
MR !3680, first word of each line: scaled Arial Keep | leave | Your | question. · Exam Serif Keep | leave | answers | question. · 4 lines each
The rules sentence is 106.12% as wide in Exam Serif as in Arial, so the scaled Arial fills each line about as fully: both take four lines, though "Your" starts line 3 in the scaled Arial and ends line 2 in Exam Serif (step 11). Exam Serif's ascent and descent are 98.4% and 27.3% of its size; divided by 1.0612 they give the overrides (step 12). The page still shows the web font in the end; nothing moved to get there.
4.2 Payments ledger MR !3681: a receipt scan's slot
A payments ledger's approval page shows the uploaded receipt scan, filled in by a script when the scan service replies, above the totals and the Approve button. The scan is 3:4. MR !3681 gave its empty slot width: 100%; aspect-ratio: 3 / 4:
receipt slot as found (width: 100%): empty slot 0 px · when the 360 × 480 scan lands: 1 shift naming #totals, #approve · Approve moved down 498 px
MR !3681 (width: 100%; aspect-ratio: 3 / 4): empty slot 480 px · when the 360 × 480 scan lands: 0 shifts · Approve moved 0 px
Before the fix the slot was empty and 0 px tall until the scan came. The entry lists the totals and the button, never the scan (step 2).
Picture it

Try it
Answer first, then check. Nothing is saved.
Question 1 of 8
A payments ledger's statement page sets its summary paragraph in the web font "Exam Serif", with Arial behind it, above a Download PDF button. The font file arrives 400 ms after it was requested, after the page's first paint. Two builds differ only in the font's font-display: one says block, the other optional.
What does a layout-shift observer record when the font arrives, in each build?
Question 2 of 8
To keep font-display: swap on ExamHall's start page without the shift, a teammate adds @font-face { font-family: "Exam Serif Fallback"; src: local("Times New Roman"); size-adjust: 84.53%; ascent-override: 116.41%; descent-override: 32.30%; line-gap-override: 0%; } and sets the rules' font-family to "Exam Serif", "Exam Serif Fallback", sans-serif. Exam Serif's ascent is 98.4% and its descent 27.3% of its size, with no line gap; the rules sentence is 118.30% as wide in Exam Serif as in Times New Roman. When the font arrives after first paint, the observer still records one shift, listing the Start button.
What is wrong?
Question 3 of 8
A payments ledger's transfer page fetches a notice, "Pending: 2 transfers awaiting approval", and writes it into an empty <div id="notice"> above the amount and the Confirm transfer button. The notice renders as one 48 px line. Today Confirm transfer jumps down when the notice lands.
Which change keeps Confirm transfer still?
Question 4 of 8
A streaming app's episode page shows a two-line synopsis, a Show full synopsis button, then the Play button. A viewer clicks Show full synopsis with the mouse; its click listener expands the synopsis at once, which pushes Play down.
What does a layout-shift observer record for that frame?
Question 5 of 8
The interviewer hands you ExamHall's new start page, sandbox/start.html: a proctoring camera preview that a script fills with a 16:9 box when the camera service replies, the rules in Exam Serif with font-display: swap, a notice line (48 px) filled from /notice.json, then Start exam. "Make Start exam stay put while all three arrive late. Keep swap, keep the notice above the button and keep the preview 16:9 at full width." Exam Serif's ascent is 98.4% and its descent 27.3% of its size, with no line gap; the rules sentence is 106.12% as wide in Exam Serif as in Arial. You have 15 minutes; say what will move first as you go.
Edit the CSS so that Start exam stays put while all three arrive late.
Question 6 of 8
A payments ledger's team adds CLS to its release checklist, read from field data at the 75th percentile of page loads, with mobile and desktop reported apart.
Which CLS value must each segment stay within to count as good?
Question 7 of 8
A box office's event pages reserve the seat map's slot with .map { width: 100%; aspect-ratio: 16 / 9; } in a 640 px column, above the Buy tickets button. When the map service replies, a script puts the venue's seat map inside .map: a 640 × 300 map for the small hall, a 640 × 420 map for the arena.
On which venue's page does Buy tickets move when the map lands?
Question 8 of 8
A chat app's field script logged five unexpected layout shifts on one page view, as (milliseconds after load, score): (200, 0.03), (600, 0.04), (1400, 0.05), (4000, 0.06), (4500, 0.02).
What CLS does this page view report?
Go deeper
Pro tips
- Log what moved, with where it was. Each
sourcesentry carriespreviousRectandcurrentRect; print them beside the node to see the move in pixels. When: a shift report names a button you did not touch (step 2). Verify (RAN, Chromium 153,tips.mjs):tip 1 #rules previousRect x 16 y 118 520 × 69 -> currentRect x 16 y 123 520 × 100 tip 1 #start previousRect x 16 y 207 113 × 29 -> currentRect x 16 y 243 113 × 29 - Move things with
transform, nottopor a margin. A toast or banner slid into view bytransformkeeps its layout position, so it shifts nothing;topon a positioned element moves its start position, and the observer records it. When: animating something into view (compositing isperf-frame-pipeline-layout-thrash's). Verify (RAN,tips.mjs):tip 2 top: 40px (position: relative) toast drawn at y 56 · 1 shift naming #toast tip 2 transform: translateY(40px) toast drawn at y 56 · 0 shifts
Source
Valid as of 2026-10-09.
- web.dev, Cumulative Layout Shift (CLS) (the curriculum row's first source), https://web.dev/articles/cls (
webdev-cls.txt). "CLS is a measure of the largest burst of layout shift scores for every unexpected layout shift that occurs during the entire lifecycle of a page." and a burst is "less than 1-second in between each shift and a maximum of 5 seconds for the total window duration" (step 4); "To provide a good user experience, sites should strive to have a CLS score of 0.1 or less." and "Good CLS values are 0.1 or less. Poor values are greater than 0.25."; "Layout shifts that occur within 500 milliseconds of user input will have the hadRecentInput flag set, so they can be excluded from calculations." (step 3). - web.dev, Optimize Cumulative Layout Shift (the row's second source), https://web.dev/articles/optimize-cls (
webdev-optimize-cls.txt). "Always include width and height size attributes on your images and video elements. Alternatively, reserve the required space with CSS aspect-ratio or similar." (step 6); "add a min-height CSS rule to reserve space" (step 8); "Even if the text is invisible, it's still laid out using the fallback font" (step 9); "font-display: optional can avoid a re-layout as the web font is only used if it is available by the time of initial layout." (step 10); "Minimize the size differences between the fallback font and the web font using the new size-adjust, ascent-override, descent-override, and line-gap-override APIs" (step 11); "Composited animations using translate can't impact other elements, and so don't count toward CLS." (tip 2). - MDN, font-display (the row's third source), https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/font-display (
mdn-font-display.txt).optional"Gives the font face an extremely small block period and no swap period.";block"Gives the font face a short block period and an infinite swap period."; "Font block period: If the font face is not loaded, any element attempting to use it must render an invisible fallback font face." (step 10). - MDN, size-adjust, https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/size-adjust (
mdn-size-adjust.txt): "All metrics associated with this font are scaled by the given percentage. This includes glyph advances, baseline tables, and overrides provided by @font-face descriptors." (steps 11–12). MDN, ascent-override, https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/ascent-override (mdn-ascent-override.txt), read for the descriptor's syntax only. - MDN, aspect-ratio, https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/aspect-ratio (
mdn-aspect-ratio.txt): "If the element is not a replaced element, then the given ratio is used." (step 6). W3C, CSS Box Sizing Module Level 4 (Working Draft), https://www.w3.org/TR/css-sizing-4/ (css-sizing-4.txt): "the automatic minimum size in the ratio-dependent axis of a box with a preferred aspect ratio that is neither a replaced element, nor a scroll container in that axis, is its min-content size". - WICG, Layout Instability API (Draft Community Group Report), https://wicg.github.io/layout-instability/ (
layout-instability.txt): "if a newly inserted element shifts content below it, the sources attribute will report only the shifted elements, and not the inserted element." (step 2); "Excluding inputs generally include mousedown, keydown, pointerdown, and change events." (step 3). Living documents, as of 2026-10-09.
Related lessons
Checked against the tools and docs as of 2026-10-09.