Interaction to Next Paint replaced First Input Delay among the Core Web Vitals in March 2024. The difference is substantial: the old metric only looked at the delay of the first interaction, while INP observes every interaction during the visit (clicks, taps, key presses) and reports a value close to the worst one. An INP up to 200 milliseconds is considered good, above 500 is poor. Anyone with a highly interactive site, such as a configurator or a members' area, notices it immediately.
The three parts of an interaction
Each interaction measured by INP is made of three times:
- input delay: the time between the user's gesture and the start of the event handlers. It is long when the main thread is already busy with another task;
- processing time: how long the
click,keydown,pointerupand similar handlers take to finish; - presentation delay: the time the browser needs to recalculate styles and layout and paint the next frame.
The user perceives the sum: they tap a button and for a moment nothing happens. Each of the three parts has different causes and remedies.
The main enemy: long tasks
The browser runs JavaScript, style calculation, layout and painting on a single main thread. A task lasting more than 50 milliseconds is considered "long" because, while it runs, nothing else can happen. If the user taps the screen during a 300 millisecond task, that time becomes input delay.
Typical sources of long tasks are the start-up of heavy frameworks, third-party scripts all executing together, manipulation of lists with thousands of items, and event handlers that do too much work in one go.
Handing control back to the browser
The central technique is called yielding: splitting the work into chunks and, between one chunk and the next, letting the browser handle input and painting. A widespread, compatible approach uses a promise with setTimeout:
function yieldToMain() {
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise(r => setTimeout(r, 0));
}
async function processRows(rows) {
for (let i = 0; i < rows.length; i++) {
updateRow(rows[i]);
if (i % 50 === 0) await yieldToMain();
}
}
scheduler.yield() is available in Chromium-based browsers and has the advantage of resuming the work with higher priority than a plain timeout; the fallback guarantees it works everywhere.
Feedback first, everything else later
A practical principle: in the event handler, do only what is needed to show the user that the action was received, then defer the rest. If a click on «Add to cart» has to update the counter, send an analytics event, save to localStorage and recalculate recommendations, only the first item is urgent.
button.addEventListener('click', async () => {
counter.textContent = ++total; // visible immediately
await yieldToMain();
sendAnalytics('added');
saveCart();
recalculateRecommendations();
});
The frame with the updated counter is painted before the secondary work starts, and INP measures exactly that frame.
Reducing presentation delay
Even with fast handlers, an interaction can feel slow if the browser has to recalculate a huge layout. The most common causes:
- a DOM with tens of thousands of nodes, where even a small class change on
bodyforces many selectors to be re-evaluated; - interleaved layout reads and writes (reading
offsetHeight, changing a style, reading again), which force several recalculations in the same frame; - inserting large blocks of HTML with
innerHTMLin response to the click.
Keeping the DOM lean, grouping reads before writes and updating only the necessary portion are fixes whose effect shows immediately.
How to find slow interactions
In the lab, the DevTools Performance panel shows the interactions track with the duration of the three parts; it is worth enabling CPU throttling to simulate a mid-range phone, because on a powerful laptop almost everything looks fast. In the field, the web-vitals library with attribution tells you the element on which the worst interaction happened and the event type: by collecting this data you discover, for example, that the bad INP comes entirely from the filter dropdown on a single page.
Common mistakes
- Optimizing only the initial load and forgetting that INP is measured across the whole visit.
- Moving work into a long
setTimeout, which postpones the problem without solving it if the task then remains monolithic. - Testing only on desktop: the difference in computing power between devices makes INP above all a mobile metric.
In short
- INP measures the latency of all interactions and reports a value close to the worst; good up to 200 ms.
- It is made of input delay, processing time and presentation delay.
- Long tasks on the main thread are the main cause: break them up and yield to the browser.
- Show visual feedback immediately and defer secondary work.
- Measure on slow devices or with throttled CPU and collect real data with attribution.