What is Interaction to Next Paint (INP)?
Interaction to Next Paint (INP) is a Core Web Vital that measures a page's overall responsiveness to user interactions.
What is Interaction to Next Paint (INP)?
Interaction to Next Paint (INP) is the Core Web Vital that measures how fast your page actually responds when someone clicks, taps, or presses a key. Unlike First Input Delay (FID)—which only looked at the first interaction—INP tracks responsiveness across the entire session and reports the worst interaction at the 75th percentile. Google replaced FID with INP in March 2024. Good is 200ms or less. 200–500ms needs work. Above 500ms is poor. This directly affects search rankings—it's one of three metrics (with LCP and CLS) in Google's page experience signals. Classic pain point: e-commerce product page where clicking "Add to Cart" takes 400ms to show any visual feedback. INP will flag that.
How it works
INP measures the gap from when a user starts an interaction to when the browser paints the next frame showing that something happened. Every interaction has three phases:
- Input delay — event sits in the queue waiting for the main thread.
- Processing time — your JavaScript actually runs.
- Presentation delay — browser recalcs styles, layout, composites, paints.
INP = input delay + processing time + presentation delay, for the worst (or near-worst) interaction you see during the session.
The browser's PerformanceObserver API with event entries exposes per-interaction timing. Google's web-vitals library (v4+) makes this dead simple:
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value, 'ms');
console.log('Worst interaction:', metric.entries[0]);
});
The metric only finalizes when the user leaves (on visibilitychange) because any later interaction could become the new worst. Chrome reports INP in CrUX (Chrome User Experience Report), which is what Google Search actually uses. Lighthouse (v12+) can simulate INP in "Timespan" mode, but field data from CrUX or RUM is what counts.
Here's the thing: a click handler that blocks the main thread for 300ms creates 300ms of processing time. But even a fast handler preceded by a 250ms main-thread task (say, some third-party script parsing) creates 250ms of input delay. Both paths wreck your INP.
When to use it
INP should be monitored on every page where you care about ranking or UX. It's not optional—it's a ranking signal.
Prioritize INP optimization when you've got:
- Heavy interactive elements (filters, modals, carousels, form submissions)
- SPAs where client-side route transitions trigger expensive re-renders
- Significant third-party JavaScript (chat widgets, analytics, ad scripts)
- E-commerce checkout flows where slow feedback kills conversion
INP matters less on:
- Purely static content (blog posts, docs)
- Pages where users mostly scroll without clicking
- Server-rendered pages with minimal client-side JS
On our projects, the biggest INP wins consistently come from three places: breaking up long tasks with scheduler.yield(), deferring non-critical third-party scripts, and moving expensive state updates off the main thread with web workers.
INP vs alternatives
| Metric | What it measures | Scope | Status |
|---|---|---|---|
| INP | Full interaction latency (input delay + processing + paint) | All interactions, worst reported | Active Core Web Vital (March 2024) |
| FID | Input delay only of the first interaction | Single interaction | Deprecated as CWV (March 2024) |
| TBT (Total Blocking Time) | Sum of blocking portions of long tasks during load | Lab-only, load phase | Lighthouse proxy for INP |
| TTI (Time to Interactive) | When main thread is consistently idle | Lab-only, load phase | Largely deprecated |
FID was easier to pass—most sites scored well because it only measured delay, not processing or paint, and only for the first tap. INP is a harder bar. When Google flipped the switch in March 2024, roughly 30% of origins that passed FID failed INP according to CrUX. TBT in Lighthouse is still a useful lab proxy (weighted 30% in performance score), but it doesn't capture post-load interactions.
Real-world example
We've shipped INP fixes on 50+ projects. Recent case: Next.js 14 e-commerce site with faceted search filters. Each filter click triggered a full product list re-render—380ms processing time on mid-range Android, pushing INP to ~450ms (poor). We made three changes: (1) wrapped the filter state update with React.startTransition to let the browser paint an immediate visual indicator, (2) used scheduler.yield() between filter logic and DOM update to break the long task, (3) lazy-loaded the third-party reviews widget that was adding 120ms of input delay during initialization. Result: INP dropped to 140ms at the 75th percentile in CrUX within 28 days. Went from "poor" to "good" and we saw measurable ranking improvement for the target keywords.