You tap "Add to basket", open the menu on your phone or pick a filter, and for a moment the page doesn't react. There is no error and no blank screen: it just takes a while to respond. That "sticky" feeling is exactly what INP (Interaction to Next Paint) measures, one of the three Core Web Vitals metrics.
We look at what it really measures, how to read it in Google's reports, how to find the source of the problem with free tools and which changes tend to work. We start with the why.
What INP is and what its thresholds are
INP measures the latency of a person's interactions with your page throughout the whole visit. It counts mouse clicks, taps on touchscreens and key presses. It does not count scrolling or hovering. For each interaction it measures the time from the moment the person acts until the browser paints the next frame showing the result (hence the name, "interaction to next paint"). It then keeps one representative value for the visit: in general, that of the slowest interaction, although on pages with a very large number of interactions a few extreme values are discarded so that a single isolated hiccup is not penalised.
INP replaced FID (First Input Delay) as a Core Web Vital in March 2024. FID had two important limitations: it only measured the first interaction, and only the wait before processing began, not how long the page took to react. A site could score well on FID and still feel slow by the third click. INP covers all interactions and the whole journey until the result is visible, so it is much closer to what people actually experience.
The official thresholds are these:
- Good: up to 200 ms.
- Needs improvement: between 200 and 500 ms.
- Poor: more than 500 ms.
The criterion is applied at the 75th percentile of real visits, separately for mobile and desktop. Put differently: for a URL (or a group of URLs) to count as good, at least three out of four visits must come in at 200 ms or less. The thresholds and the metrics that make up Core Web Vitals may be revised over time, so confirm the current values in the official documentation on web.dev and Google Search Central.
The three phases of an interaction
An interaction is not a single block, and understanding that is key because each phase has different causes and different fixes:
- Input delay: from the moment the person acts until the browser can start running your event handlers. It grows when the main thread is busy with something else: a long task from a script, a third-party load, framework hydration.
- Processing time: how long your handlers (
click,keydown,pointerup...) take to run, plus the work they trigger. Expensive calculations and large DOM updates weigh heavily here. - Presentation delay: from the end of the handlers until the browser paints the next frame. It includes recalculating styles, working out the layout and painting. It grows with huge DOMs, costly CSS and changes that affect half the page.
An example with made-up figures, just to illustrate: a filter takes 60 ms to start being handled, 90 ms to run its code and 110 ms to be painted, 260 ms in total. Knowing that most of it sits in presentation sends you to look at the DOM and the CSS, not to optimise the handler.
Lab data and field data
INP is, by nature, a field metric: it depends on what each person taps, on which device and at what moment. A lab test doesn't know what you are going to touch, so it cannot calculate it the same way.
Field data comes from CrUX (the Chrome User Experience Report), which anonymously collects the experience of real Chrome users who have agreed to share it, aggregated over a rolling 28-day window. That data feeds:
- The Core Web Vitals report in Search Console, which groups similar URLs and separates mobile from desktop.
- PageSpeed Insights, in its real-user experience section, which can show data for the specific URL and for the whole origin if there is enough traffic.
Two practical consequences. Because the window is 28 days, an improvement takes weeks to show up fully. And if your site has little traffic, CrUX may not have enough data and the report may appear empty.
Lab data is obtained in a controlled environment, such as Lighthouse (inside PageSpeed Insights and DevTools). Lighthouse in navigation mode does not measure INP, because it doesn't simulate interactions; to get an approximation of responsiveness, people usually look at Total Blocking Time, which is only an indirect indicator. The useful rule: field data tells you whether there is a problem and the lab helps you find out where it is. Report and section names change from time to time: if in doubt, check the documentation.
How to diagnose it step by step
- Confirm the problem in the field. Open Search Console, go to the Core Web Vitals report, and see whether any URLs have INP in "Needs improvement" or "Poor", on mobile or desktop. Cross-check with PageSpeed Insights for your most important pages. If there isn't enough data, you will have to rely on your own measurement (step 4).
- Reproduce the slowness in Chrome DevTools. Open an incognito window (so extensions don't add noise), open the Performance panel and turn on CPU throttling, for example 4x, because your computer is much faster than a mid-range phone. Record, perform the interaction you suspect (opening the menu, applying the filter, pressing the button) and stop recording. In the interactions track you will see each one with its duration broken down into the three phases. Recent versions of DevTools also include a live metrics view that shows your local INP as you browse.
- Look for long tasks. A long task is any task that blocks the main thread for more than 50 ms, and DevTools marks them visually. Zoom into the one that coincides with your interaction, open the call tree (Call Tree or Bottom-Up) and see which file and which function accumulate the most time. Work out whether it is your own code, the theme or a plugin, or a third party.
- Measure real visits with the web-vitals library. This is Google's library for collecting Core Web Vitals in the browser. Its attribution build tells you which element triggered the slow interaction and how long each phase lasted:
import {onINP} from 'web-vitals/attribution';
onINP(({value, attribution}) => {
console.log('INP', value);
console.log('Element', attribution.interactionTarget);
console.log('Input', attribution.inputDelay);
console.log('Processing', attribution.processingDuration);
console.log('Presentation', attribution.presentationDelay);
});
Property names have changed between versions of the library, so check its documentation before copying this. In production, instead of console.log, send the result to your analytics (for example with navigator.sendBeacon or as a GA4 event). After a few days you will have a ranking of which pages and elements give the worst INP.
A note of caution: the Long Animation Frames (LoAF) API describes, for each slow frame, which scripts contributed and how long they took. It helps to point at a specific script, especially a third-party one, but its support is limited to Chromium-based browsers and may evolve. Check compatibility before depending on it.
The most common causes
- Heavy JavaScript on the main thread. Huge bundles, lots of code that runs as soon as the page loads, whole libraries pulled in to use a single function.
- Third-party scripts. Tag managers with many tags, chats, pixels, A/B tests, maps, embedded videos, cookie banners. They share the same thread as your code and you don't control what they do.
- Hydration. In frameworks that render on the server (React, Vue and similar), the page appears quickly but doesn't fully respond until the JavaScript "hydrates" the components. A click in that window suffers a high input delay.
- A huge DOM. Thousands of nodes make every style and layout recalculation more expensive, and that lengthens the presentation delay.
- Slow handlers. Filtering hundreds of elements inside
click, processing a giant JSON, writing tolocalStoragesynchronously or firing heavy analytics before updating the screen. - Layout thrashing. Alternating reads of measurements (
offsetHeight,getBoundingClientRect()) with style writes inside a loop forces the browser to recalculate the layout over and over. - WordPress: plugins and page builders. It is common for plugins to load their JavaScript on every page even if they are only used on one, and for page builders and multipurpose themes to generate a deeply nested DOM, with sliders, animation effects and pop-ups. Not all of them are guilty, so measure before pointing fingers: in a test environment, disable them one by one and compare.
Concrete fixes
Break up long tasks and yield to the main thread
The browser can only paint and handle input between tasks. If a task lasts 400 ms, everything that happens during that time waits. The solution is to chop the work into pieces and "yield" the thread between them so the browser can breathe. The modern option is scheduler.yield(), which keeps the priority of whatever comes next, but it isn't available in every browser, so it is used with a fallback based on setTimeout:
function yieldToMain() {
if ('scheduler' in globalThis && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}
async function processList(items) {
let lastYield = performance.now();
for (const item of items) {
process(item);
if (performance.now() - lastYield > 40) {
await yieldToMain();
lastYield = performance.now();
}
}
}
The idea is not to go much beyond a few tens of milliseconds without giving control back. Check the current compatibility of scheduler.yield() on MDN or caniuse before deciding whether the fallback is needed in your case.
Do less work per interaction
The first thing that should happen on a tap is visual feedback: a loading state, a button that changes, a panel that opens. The rest can wait one frame:
button.addEventListener('click', async () => {
button.classList.add('loading');
await yieldToMain();
applyFilters();
});
Other ways to cut the work: debounce search fields that filter as you type, move heavy calculations to a Web Worker, avoid re-rendering everything when only one part changes, virtualise very long lists and, in React, mark non-urgent updates with startTransition.
Keep third-party scripts under control
Take stock of what is loaded (the Network tab in DevTools) and ask of each script whether it still earns its place. Remove what isn't used, load with async or defer whatever can wait, and delay anything non-critical (a chat, a map) until there is interaction or until startup is over. For embedded videos, a placeholder image that loads the player only on click keeps its code out of every visit. With anything tied to legal obligations, such as cookie consent, the aim isn't to remove it but to choose a lightweight solution. Measure before and after each change to know what really helped.
Reduce the cost of rendering
If the bottleneck is in presentation, tackle the DOM size and the painting work. On long sections that sit off-screen, content-visibility lets the browser skip rendering them until they are needed:
.long-section {
content-visibility: auto;
contain-intrinsic-size: auto 800px;
}
The second value is a height estimate so the scrollbar doesn't jump; test it well, because a badly wrong estimate can be noticeable. And if you have layout thrashing, do all the measurement reads in one pass and all the style writes in another, instead of alternating them inside the same loop.
Animate with CSS and use passive listeners
Animations using transform and opacity defined in CSS can usually be resolved without touching the main thread on every frame, whereas a JavaScript animation that changes top or left on every pass saturates it. For scroll, touch or wheel events, register the listeners as passive, with { passive: true }, when you don't need to cancel the default behaviour. Scrolling doesn't count towards INP, but a heavy scroll handler can block the thread just when someone is trying to tap something.
What to expect for SEO, and a final checklist
INP is part of page experience. Google has said that Core Web Vitals are one of the signals its ranking systems consider, but also that the relevance of the content weighs far more. Think of INP as a light signal: improving it won't push you up the results on its own, and we can't promise specific outcomes. What you usually do notice is the experience: less frustration, forms and baskets that respond better and, as a result, fewer abandonments. And because field data is calculated over 28 days, expect to see the effect in Search Console after several weeks.
A checklist to run through:
- You have looked at INP in Search Console and PageSpeed Insights, on mobile and on desktop.
- You have reproduced the slow interaction in DevTools with the CPU throttled.
- You know which phase the time goes in: input, processing or presentation.
- You have located the long tasks and the script responsible, and you measure with real users.
- You have broken up the long tasks and yield to the main thread when needed.
- The handler gives visual feedback first and leaves the heavy work for later.
- You have reviewed every third-party script and removed or delayed the dispensable ones.
- On WordPress, you have checked which plugins and page builders load JavaScript where it isn't needed.
- You have reduced the DOM or applied
content-visibilitywhere it makes sense. - Animations use CSS (
transform,opacity) and scroll listeners are passive. - You have noted the date of the change and will review the data in 28 days.
Frequently asked questions
Why is my PageSpeed score high and my INP still bad?
Because the Lighthouse performance score is a lab test that doesn't simulate your real interactions, whereas field INP comes from real visits, with slower devices and usage you haven't anticipated. Trust the field data to know whether there is a problem and use the lab to investigate the cause.
What do I do if no INP data appears for my site?
It usually means there isn't enough Chrome traffic for CrUX to publish data for that URL. You can look at the data for the whole origin if it is available, or measure it yourself with the web-vitals library and send the values to your analytics.
How long does it take for an INP improvement to show?
In your DevTools tests or your own measurement, the improvement is visible straight away. In Search Console and CrUX data it takes longer, because they are calculated over a rolling 28-day window: old data is gradually replaced by new data. The sensible approach is to wait several weeks before drawing conclusions.