What Is the LCP Element? How the Browser Picks It
You can't fix your Largest Contentful Paint until you know which element the browser actually picked — and it's often not the hero image you've been optimizing. Here's how the browser really chooses your LCP element, the surprising exclusions that trip teams up, and how to find yours in the field.
A few years ago I watched a capable team spend three sprints on their homepage hero image. New formats, a preload tag, CDN resizing — textbook work. Their LCP didn't move. When we finally pulled attribution data from the field, the LCP element for most users wasn't the hero at all. It was the cookie consent banner's headline text, rendering late because its script loaded late.
Three weeks of correct optimizations applied to the wrong element. That's the founding lesson of this series: you cannot optimize LCP until you know which element the browser picked — and it's often not the one you think.
The definition, precisely
LCP is the render time of the largest image, video poster frame, or text block visible in the viewport, measured from navigation start. Good is ≤ 2.5 seconds, poor is > 4.0 seconds — at the 75th percentile of field data. The nuance most people miss: LCP is a moving target during load. The browser emits a series of LCP candidates as progressively larger elements render — first a nav logo, then a headline, then a hero image. Only the final candidate before user interaction counts. Your LCP element can change based on what happens to load fastest, which is why it differs between users, devices, and cache states.What counts — and the surprises
LCP measures when the biggest visible piece of content on the screen finishes loading. This could be:- A large image
- An image inside an SVG graphic
- A video's cover image (poster)
- A background image used in a section of the page
- A large block of text, like a headline or banner text
Finding YOUR element
In the lab: DevTools Performance panel, record a load, click the LCP marker — the element highlights. Lighthouse lists it under diagnostics. But here's the trap: your lab LCP element is frequently not your field LCP element. Different viewport sizes, logged-in states, ads, personalization, cache states. The lab shows you one movie frame; the field is the whole distribution. The real answer is the attribution build of the web-vitals library:import { onLCP } from 'web-vitals/attribution';
onLCP(({ value, attribution }) => {
sendToAnalytics({
metric: 'LCP',
value,
element: attribution.element, // e.g. "div#hero > img"
url: attribution.url, // resource URL if it's an image
});
});