# Core Web Vitals (LCP, INP, and CLS)

> What LCP, INP, and CLS measure, the 2.5 s, 200 ms, and 0.1 thresholds at the 75th percentile, which engines expose each entry, and the levers a PWA has.

Core Web Vitals are three field metrics that Google's tooling treats as the definition of a
good page experience: Largest Contentful Paint for loading, Interaction to Next Paint for
responsiveness, and Cumulative Layout Shift for visual stability. A page passes when the 75th
percentile of real visits is "good" on all three, and a PWA's service worker, task scheduling,
and layout reservations are the levers that move each one.

## How it works

Each metric is a `PerformanceEntry` type exposed through `PerformanceObserver`, aggregated
over a visit, and reported from the field by the `web-vitals` library or by the Chrome User
Experience Report (CrUX). The thresholds come from the Chrome team's calibration against
user-perceived quality ([Web Vitals](https://web.dev/articles/vitals), web.dev).

| Metric | What it measures | Good | Needs improvement | Poor | Entry type |
|---|---|---|---|---|---|
| LCP | Render time of the largest image or text block in the viewport, relative to navigation start | ≤ 2.5 s | ≤ 4.0 s | > 4.0 s | `largest-contentful-paint` (Chrome 77, Firefox 122, Safari 26.2) |
| INP | The slowest interaction's latency from input to the next paint, over the whole visit, with a high-percentile cut for pages with many interactions | ≤ 200 ms | ≤ 500 ms | > 500 ms | `event` with `interactionId` (Chrome 96, Firefox 144, Safari 26.2) |
| CLS | Sum of unexpected layout shift scores in the worst session window | ≤ 0.1 | ≤ 0.25 | > 0.25 | `layout-shift` (Chromium only) |

INP replaced First Input Delay as the responsiveness metric in March 2024
([Interaction to Next Paint](https://web.dev/articles/inp), web.dev); FID measured only the
input delay of the first interaction, whereas INP includes processing time and the paint of
the frame after it. Thresholds apply at the 75th percentile of page views, separately for
mobile and desktop.

### Field and lab data

INP and CLS accumulate across a visit, so a single scripted load in Lighthouse or WebPageTest
cannot reproduce them; lab tools report Total Blocking Time and a load-time CLS as proxies.
CrUX publishes the field distributions for origins with enough traffic, and the `web-vitals`
library gives a page its own numbers through `onLCP()`, `onINP()`, and `onCLS()` callbacks.
Use the lab to find causes and the field for the verdict.

### Support for the entry types

Measuring in the field depends on the engine exposing the entry: `largest-contentful-paint` is
in Chrome 77, Firefox 122, and Safari 26.2; `event` entries with `interactionId` in Chrome 96,
Firefox 144, and Safari 26.2; `layout-shift` is Chromium-only (BCD `api.LargestContentfulPaint`,
`api.PerformanceEventTiming.interactionId`, `api.LayoutShift`). CrUX, and therefore the field
numbers search tooling reports, is collected from Chrome only.

### Levers a PWA controls

- **LCP.** A cache-first or stale-while-revalidate strategy in the service worker serves the
  shell and the hero image from Cache Storage on repeat visits, removing the network from the
  critical path; see [precaching](/reference/performance/precaching/). On first visits the
  levers are `fetchpriority="high"` on the LCP image, no lazy loading above the fold, and a
  server response that includes the image URL in the HTML rather than in a script.
- **INP.** Long tasks on the main thread delay the next paint. Split work with
  `scheduler.yield()` or `setTimeout` chunks, move parsing and hydration off the interaction
  path, and keep event handlers short; see [code splitting](/reference/performance/code-splitting/)
  for shrinking what runs at startup.
- **CLS.** Reserve space before content arrives: `width` and `height` or `aspect-ratio` on
  images and embeds, fixed-height slots for late banners, and `font-display: optional` or a
  size-matched fallback so a web font swap does not reflow text; see
  [font and image optimization](/reference/performance/fonts-images/).

## Observed behaviour

The three entry types are visible in Chrome without any library. The snippet below logs each
metric as the browser reports it and degrades to "unsupported" where an engine lacks the entry
type, which is the case for `layout-shift` outside Chromium.

```js
function observe(type, handler) {
  if (!('PerformanceObserver' in window)
      || !PerformanceObserver.supportedEntryTypes.includes(type)) {
    return false; // entry type not supported here: report nothing for this metric
  }
  new PerformanceObserver((list) => list.getEntries().forEach(handler)).observe({ type, buffered: true });
  return true;
}

observe('largest-contentful-paint', (e) => console.log('LCP candidate', e.startTime, e.element));
observe('layout-shift', (e) => { if (!e.hadRecentInput) console.log('shift', e.value, e.sources); });
observe('event', (e) => { if (e.interactionId) console.log('interaction', e.name, e.duration); });
```

`largest-contentful-paint` fires more than once as larger candidates render; the last entry
before the first interaction or tab change is the LCP. `layout-shift` entries carry
`hadRecentInput`, which excludes shifts within 500 ms of a user input, and `sources` naming the
shifted nodes.

:::observed
Chrome DevTools, Performance panel, opens on a **Live metrics** view (Chrome 129 onwards) that
shows the current page's LCP, CLS, and INP as they change, next to the 75th-percentile field
values from CrUX for the same URL and origin when the site has enough traffic ([What's New in
DevTools (Chrome 129)](https://developer.chrome.com/blog/new-in-devtools-129),
developer.chrome.com). The INP figure updates after each interaction and names the slowest one,
which makes the panel the quickest way to confirm which click or key press is setting the
page's INP.
:::

## See also

- [Web Vitals](https://web.dev/articles/vitals) (web.dev)
- [Interaction to Next Paint (INP)](https://web.dev/articles/inp) (web.dev)
- [Largest Contentful Paint API](https://w3c.github.io/largest-contentful-paint/) (w3.org)
- [Precaching strategies](/reference/performance/precaching/)
- [Code splitting with dynamic import()](/reference/performance/code-splitting/)
- [Font and image optimization](/reference/performance/fonts-images/)
- [Lighthouse and the removed PWA category](/reference/performance/lighthouse/)