# Core Web Vitals

## What it is

Google's web.dev documentation picks out three metrics from its wider Web Vitals
programme and calls them core: Largest Contentful Paint for loading, Interaction
to Next Paint for responsiveness and Cumulative Layout Shift for visual
stability. Each describes something a visitor notices, applies to every page and
can be measured in the field, in real visitors' browsers.

Largest Contentful Paint (LCP) is the time from the start of navigation to the
moment the largest image, text block or video visible in the viewport has
rendered. It is good at 2.5 seconds or less and poor above 4 seconds.

Interaction to Next Paint (INP) watches every click, tap and key press during a
visit and takes the whole wait the visitor sees: the delay before the handlers
start, the time they run and the time until the next frame is painted. The
page's INP is the slowest of those, with one outlier dropped for every 50
interactions. Scrolling, hovering and zooming are left out. It is good at 200
milliseconds or less and poor above 500. INP replaced First Input Delay on 12
March 2024, having been introduced as an experimental metric in May 2022; First
Input Delay measured only the delay before the first interaction's handlers
began.

Cumulative Layout Shift (CLS) scores visible elements that move without being
asked to. Each shift scores the share of the viewport the moving elements cover
multiplied by how far they move. Shifts less than a second apart, up to five
seconds in all, form a session window, and CLS is the total of the worst window.
Shifts within 500 milliseconds of a tap, click or key press are excused;
scrolling and dragging excuse nothing. It is good at 0.1 or less and poor above
0.25.

The verdict is read at the 75th percentile of page loads, separately for mobile
and desktop, and a page passes only when all three are good there. A "good" line
was accepted only if at least a tenth of origins already met it.

Underneath are browser interfaces with their own specifications, the Largest
Contentful Paint, Layout Instability and Event Timing APIs, and a few lines show
the raw material of CLS on any page:

```js
new PerformanceObserver((list) => {
    for (const shift of list.getEntries()) {
        if (!shift.hadRecentInput) console.log(shift.value, shift.sources);
    }
}).observe({ type: "layout-shift", buffered: true });
```

That logs individual shifts, not the metric; for the three as Google's tools
report them, web.dev points to the `web-vitals` library.

The term is easy to confuse with the Lighthouse performance score, which comes
from one simulated load in a lab, with no visitor in it; the Core Web Vitals
assessment describes real visits at the 75th percentile, and a page can do well
on one and badly on the other.

## Why it matters

The definitions leave open where the numbers come from, and that is the decision
a site has to make: steer by lab estimates, or pay for field data.

Field data is what the thresholds are written for. The
[Chrome User Experience Report](https://developer.chrome.com/docs/crux) collects
anonymised measurements from real Chrome users and feeds
PageSpeed Insights and Search Console's Core Web Vitals report without the site
adding anything, but only once a page or origin has enough visitors. The
alternative is a script in the visitor's browser that sends every reading to a
server: a privacy decision, and a cost carried on every page view.

Lab data costs neither. Lighthouse loads the page on a simulated device and
network, before a change ships and as often as wanted. What it measures is a
visit nobody made. It sees LCP and CLS during the load, but with nobody pressing
anything it has no INP; web.dev offers Total Blocking Time as the closest lab
measure and says it is no substitute.

The workable split is both, held apart: lab numbers as budgets that catch a
change for the worse, field numbers, where they exist, as the only verdict on
what visitors get.

## What it looks like in practice

A planning record shows a Core Web Vitals job was considered and declined: the
Chrome UX Report needs an API key and enough traffic to report, and the pages
are already held to size budgets in the quality gate. The site gathers none
itself either: its credits page says it
[runs one script, of about a kilobyte, and no analytics](/credits/), and that
script, the list filter, is the only one that could hold up a click or a key
press. The same page points readers to PageSpeed Insights, which shows
measurements from real visits once a page has enough of them.

What the site measures instead is the lab. Its Lighthouse configuration loads a
fixed list of pages three times each under simulated mobile throttling and
reports a failure when one of the English or Danish pages crosses any of these
lines: LCP over 4,200 milliseconds, First Contentful Paint over 1,600, CLS over
0.02 and Total Blocking Time over 100, with slightly wider paint limits on the
Ukrainian pages. These are budgets, not the field thresholds, written after the
run-to-run spread had been measured at 299 milliseconds at worst and 80 at best,
because a line inside that spread passes or fails by chance. The project's notes
put most of the LCP down to render delay behind one render-blocking stylesheet.
Weight that arrives before any content had already been cut:
[the stylesheet bundle weighed 87 KB with a base64 font inside it and 31 without](/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/).
The runs take minutes, so they happen on demand, outside the gate.

The CLS budget, five times stricter than the 0.1 line, caught a case worth
keeping. One category page failed at 0.107. The owner opened it, saw nothing
wrong and said so. Lighthouse had named the parallax gears as the culprit, and
the first written diagnosis believed it. A controlled test with a real wheel
gesture scored 0.2477 whether the gears moved or not, while the heading and the
last paragraph moved by zero pixels throughout. The cause was the footer: it is
fixed to the viewport and changes height as its colophon opens, and the metric
scores that box against the viewport although a fixed element cannot push
anything. The budget stayed where it was; if the number is ever silenced, the
plan is an exception for that one address.

## Where it goes wrong

**A lab report leaves INP out and the page is assumed responsive.** Lighthouse
shows LCP and CLS, Total Blocking Time is low, and slow clicks in production
come as a surprise. A simulated load has nobody interacting, and Total Blocking
Time only counts main-thread blocking during that load. Read it as a warning
light, time real interactions by hand in the browser's performance tools, and
judge INP from field data where there is any.

**The element a tool blames turns out to be innocent.** The report names a node
for a layout shift, changing that node moves the score by nothing, and the text
on screen stays put. Attribution names nodes, not causes; the shift may belong
to a fixed overlay that changes height, which counts as movement and is not
excused by scrolling. Switch suspects off one at a time, measure whether content
moves, and animate size with `transform`, which web.dev recommends because it
causes no layout shift.

**Content jumps down as images and late arrivals load.** Readers lose their
place and field CLS climbs past 0.1. web.dev lists the usual causes: images and
video of unknown size, web fonts that render larger or smaller than the
fallback, and widgets that resize themselves. Give every image `width` and
`height` attributes so the browser reserves its box, as this site's image
partials do, and reserve space for anything inserted later.

**A healthy average hides a failing assessment.** A dashboard shows a mean LCP
well under 2.5 seconds, yet the page is rated as needing improvement. The
assessment uses the 75th percentile, per device class, and a slow quarter of
mobile visits disappears inside a mean that includes fast desktops. Report and
target the 75th percentile, split by device.

## Further reading

- [Web Vitals](https://web.dev/articles/vitals) — the three metrics, their
  thresholds and the tools that report them.
- [Defining the Core Web Vitals metrics thresholds](https://web.dev/articles/defining-core-web-vitals-thresholds)
  — why the 75th percentile, and how each line was tested.
- [Interaction to Next Paint](https://web.dev/articles/inp) — what counts as an
  interaction and how INP differs from First Input Delay.
- [Largest Contentful Paint API](https://wicg.github.io/largest-contentful-paint/)
  — which elements are candidates and when their time is taken.
- [Layout Instability API](https://wicg.github.io/layout-instability/) — the
  layout shift score, recent input and the sources of each shift.
- [Event Timing API](https://w3c.github.io/event-timing/) — how interaction
  latency is exposed to scripts.

<https://engineer.company/glossary/core-web-vitals/>
