INP: What the 200ms Threshold Measures and How to Miss It
INP measures the worst interaction on a page, not the average. Why 200ms is the bar, and the three phases that consume it.
Table of contents
INP does not report your average interaction. On most pages it reports close to your worst one — and a page where 99 clicks respond in 40ms and one takes 900ms is a page with an INP problem.
That single design decision is what makes INP harder to pass than the metric it replaced.
Key takeaways#
- Good is 200ms or less at the 75th percentile of real users. 200–500ms needs improvement; above 500ms is poor.
- INP measures the full interaction: input delay, processing time, and presentation delay — ending when the next frame paints.
- It reports roughly the worst interaction on the page, discounting outliers only on pages with many interactions.
- It replaced First Input Delay in March 2024. FID measured only the delay before the first handler ran, which passed on almost every site.
- The fix is nearly always yielding to the main thread, not making the handler faster.
What INP measures#
An interaction is a click, a tap, or a key press. INP measures the time from the user's input to the moment the browser paints the next frame reflecting it, and splits into three phases:
- Input delay — from the input until the event handler can start. The main thread was busy with something else.
- Processing time — the event handlers themselves running.
- Presentation delay — from the handlers finishing until the browser paints the resulting frame. Style, layout, paint, and any other main-thread work queued ahead of the frame.
All three count. This is where INP is genuinely different from First Input Delay, which measured only phase one — a handler could take 800ms and FID would report a good score, because it stopped counting the moment the handler began.
| Metric | Good | Needs improvement | Poor |
|---|---|---|---|
| INP | ≤ 200ms | 200–500ms | > 500ms |
Like all Core Web Vitals, the threshold is assessed at the 75th percentile of page loads from real users. Three-quarters of your visits must be at or under 200ms.
Why is my INP bad when my handlers are fast?#
Because the phase that usually dominates is not the one you profiled. Two causes account for most of it.
Presentation delay. The handler updated state in 5ms, but a framework re-render, a layout thrash, or an unrelated task queued ahead of the next frame pushed the paint out by 300ms. The handler is not the problem; what runs between the handler and the paint is.
Input delay from long tasks. A user clicks while the main thread is halfway through a 400ms analytics initialisation or a large hydration chunk. Nothing about the click is slow — it simply could not start for 400ms.
Both are invisible if you measure only the handler.
The pattern that fixes most of it#
Split the work into what the user must see immediately and everything else, then yield between them:
button.addEventListener('click', async () => {
// Visible feedback first — this is what INP measures.
setActive(true);
// Let the browser paint before doing the expensive part.
await yieldToMain();
recalculateTotals();
sendAnalytics();
});
function yieldToMain() {
if ('scheduler' in globalThis && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}The interaction now ends at the paint following setActive(true). The heavy work still happens; it just stops being part of the measured interaction.
scheduler.yield() is the better path where available — it yields to the browser but keeps your continuation ahead of unrelated queued tasks, so you do not pay a scheduling penalty for being polite. setTimeout(resolve, 0) is the fallback and puts you at the back of the task queue.
Finding the slow interaction#
INP is a field metric. Lighthouse cannot give you one, because there is no user clicking anything — a lab tool can report a total blocking time, which correlates, but it is not INP.
Two places to look:
Chrome User Experience Report, via PageSpeed Insights or Search Console's Core Web Vitals report. This is the data Google actually assesses, at the 75th percentile, aggregated over 28 days. It tells you whether you have a problem and on which page groups. It will not tell you which element.
Your own field data, using the web-vitals library. This is what identifies the element:
import { onINP } from 'web-vitals/attribution';
onINP(
(metric) => {
const { interactionTarget, interactionType, inputDelay, processingDuration } =
metric.attribution;
sendToAnalytics({
value: metric.value,
target: interactionTarget,
type: interactionType,
inputDelay,
processingDuration,
});
},
{ reportAllChanges: false },
);The attribution build is the part worth adopting. A number tells you INP is 480ms; interactionTarget tells you it is the filter dropdown, and the phase breakdown tells you whether to attack input delay or presentation delay. Without it you are guessing at which of dozens of handlers is responsible.
The usual culprits#
- Large synchronous work in a handler — sorting, filtering, or serialising a big collection on click.
- Layout thrashing — reading
offsetHeightafter a write, in a loop, forcing repeated synchronous layout. - Hydration during early interaction. A click landing while a framework is hydrating waits for it. This is a common cause of poor INP that has nothing to do with the handler.
- Third-party scripts monopolising the main thread. Analytics, tag managers, chat widgets, consent banners.
- Rendering the whole list on every keystroke in a search field, rather than debouncing or virtualising.
Why FID was replaced#
First Input Delay measured the delay before the first input handler could begin, and nothing after. That made it easy to pass by accident — most sites scored "good" on FID regardless of how sluggish they felt, because the metric ignored the handler, the re-render, and the paint.
INP fixed three things at once: it counts the whole interaction through to paint, it considers every interaction rather than only the first, and it reports near the worst rather than the first. The result is a metric that tracks the actual experience of using a page, and one that a lot of sites fail.
INP became a Core Web Vital in March 2024, replacing FID.
As of August 2026#
The thresholds are unchanged: 200ms for good, 500ms for the poor boundary, assessed at the 75th percentile of field data over a 28-day window. For where INP sits alongside LCP and CLS, see the Core Web Vitals guide.
Tags
- Performance
- Core Web Vitals
- JavaScript
- UX