Interaction to Next Paint (INP) became a Core Web Vital in March 2024, replacing First Input Delay (FID). It measures responsiveness — how long the page takes to visually react after a user interacts with it. Unlike FID, which only measured the very first interaction, INP measures every interaction during the visit and reports the worst (or near-worst).
Good INP is under 200 milliseconds. "Needs improvement" is 200–500ms; over 500ms is poor. Most sites that passed FID comfortably are now struggling with INP because it's a much stricter test.
Why INP Replaced FID
FID had two problems:
- It only measured the first interaction. Sites that were fast at first input but slow on subsequent ones got a clean FID score despite poor real-world experience.
- It only measured the input delay (time from input to handler running), not the time until the user actually saw a response. A handler that fired in 50ms but then took 500ms to update the screen would score perfectly on FID.
INP fixes both. It measures every interaction — clicks, keypresses, taps — and the metric reported is essentially the worst (technically, the 75th-percentile if there are many interactions). And it measures the full latency: from input to the next visual frame after the handler completes.
Result: sites with heavy JavaScript, slow event handlers, or thread blocking get a much harder time. Many sites that scored well on FID now show poor INP.
What INP Actually Measures
For each interaction, the browser measures:
- Input delay — time from input to event handler running.
- Processing time — time the event handler takes to execute.
- Presentation delay — time from handler completion to next visual frame.
INP = total of all three for the worst observed interaction during the visit.
The Three Causes of High INP
Cause 1: Long JavaScript Tasks
If the main thread is busy running JavaScript when the user clicks, the click event waits. A 500ms task running when the user clicks → 500ms input delay before anything else can happen.
Fixes:
- Break long tasks into smaller ones. Use
setTimeout(0)orrequestIdleCallbackto yield back to the browser between chunks. - Use the new
scheduler.yield()API in supporting browsers — explicitly yields to higher-priority work. - Move heavy computation off the main thread with Web Workers.
- Lazy-load scripts that aren't needed for interactivity. Analytics, A/B testing, marketing pixels often load eagerly when they could load after first interaction.
Cause 2: Heavy Event Handlers
The click handler itself does too much work. A click that triggers a complex re-render, an expensive API call serialised on the main thread, or a heavy recalculation pushes processing time well over 100ms.
Fixes:
- Optimise the handler. Profile what it's doing; remove unnecessary work.
- Defer non-critical work. If clicking "add to cart" needs to update the cart count and also fire 6 analytics events, do the count update first and defer the analytics with
requestIdleCallback. - Use optimistic UI. Update the visual state immediately based on the expected result; reconcile with the actual server response asynchronously. The user sees the response immediately even if the actual work hasn't finished.
- Avoid synchronous layout thrashing. Reading and writing DOM properties in a loop (read, write, read, write) forces layout recalculation each iteration. Batch reads, then batch writes.
Cause 3: Slow Render After Handler Completes
The handler finished, but the next frame takes too long. Common causes: large DOM updates, expensive CSS recalculation, complex animations starting after interaction.
Fixes:
- Avoid large DOM mutations in handlers. Inserting hundreds of elements at once forces a major layout recalc. Virtualise long lists; paginate; insert progressively.
- Use
content-visibility: autoon off-screen content to skip its layout work. - Avoid forcing reflow by reading layout properties (offsetTop, offsetWidth, getBoundingClientRect) after writing to the DOM.
- Use CSS containment (
contain: layout) on independent components so layout work doesn't cascade.
The INP Killers in Common Frameworks
React with synchronous state updates
React 18+ has automatic batching and concurrent rendering, but plenty of apps still use synchronous patterns. Heavy state updates triggered by clicks block the main thread. useTransition marks updates as non-urgent, letting React yield to higher-priority work.
Framework hydration on demand
Pages that render statically and only hydrate certain components on first interaction can have terrible INP for the first interaction (the entire framework runtime needs to load and execute). Astro's "islands" architecture and similar patterns can produce 1+ second INP for the first interaction unless carefully optimised.
Third-party scripts
Heavy analytics SDKs, ad networks, A/B testing tools, customer support widgets — each one runs on the main thread and blocks interactions while it's busy. The combined weight of multiple third-party scripts is the leading cause of poor INP on otherwise-fast sites.
Audit ruthlessly: is each script worth the INP cost? Many can be loaded after first interaction (when the user has already seen the page) rather than on initial page load.
Measuring INP
Three sources:
- Site Speed Check — synthetic measurement on demand.
- Chrome DevTools Performance panel — record an interaction, see exactly which task ran when. Identifies the long-running JS that's causing high INP.
- PageSpeed Insights field data — real-user INP from the Chrome User Experience Report (CrUX). This is what Google uses for ranking.
Key gotcha: lab measurements (Lighthouse, Site Speed Check) can show different INP than field data. Lab tests run a clean page on a fast device with limited interactions; field data captures real users on real devices interacting unpredictably. Always validate against field data.
The Pragmatic INP Optimisation Path
- Audit your third-party scripts. Anything you can defer or load after first interaction is the highest-leverage change. Most sites have 3-5 scripts that, combined, account for 50%+ of INP.
- Profile your worst interaction (the one slowest to respond). Open DevTools, record it, see which task is the bottleneck.
- Apply the right fix based on the bottleneck — break tasks, optimise handlers, reduce DOM work.
- Re-measure. INP is noisy in lab tests; field data is the source of truth but takes 28 days to update.
- Repeat for the next-worst interaction. INP improvement is iterative; you can't fix everything at once.
The Modern Mental Model
For LCP and CLS, the goal was loading: fast first paint, no shifting. For INP, the goal is responsiveness: every click, tap, and keypress feels instant. The two goals can conflict — eager-loading code makes INP better but LCP worse; lazy-loading helps LCP but can hurt INP for first interaction.
The balance is usually: render the page fast (good LCP), defer heavy interactivity setup until after first paint (good INP for early interactions), then optimise the handlers themselves so they stay fast forever (good INP throughout the visit).
Run any URL through Site Speed Check for the lab measurement, then check PageSpeed Insights for the field data. The number is your goal — under 200ms, every interaction.