Front-end3 min read

INP in practice: finding and fixing slow clicks

Interaction to Next Paint measures how long people wait for a page to respond. How to measure INP in the field and break up long tasks.

inp

Illustrative photo: Unsplash

Since March 2024 Interaction to Next Paint has been one of the three Core Web Vitals, replacing FID. The difference matters: FID only measured the delay of the first interaction, while INP looks at every click, tap and key press during the whole visit — and reports one of the worst.

What INP actually measures

Every interaction has three parts. Any of them can be the slow one, so first find out which.

  • Input delay — time before the browser even starts handling the event because the main thread is busy.
  • Processing time — time spent running your handlers.
  • Presentation delay — style recalculation, layout and painting the next frame.
RatingINP (75th percentile)
Good≤ 200 ms
Needs improvement200–500 ms
Poor> 500 ms

Measure in the field, not just in the lab

Lighthouse does not click for your users, so it cannot show real INP. You need data from real sessions. The easiest way is the web-vitals library with its attribution build, which tells you straight away which element and which phase was slow.

import { onINP } from 'web-vitals/attribution';

onINP(({ value, attribution }) => {
  const { interactionTarget, inputDelay, processingDuration, presentationDelay } = attribution;
  navigator.sendBeacon('/vitals', JSON.stringify({
    value, interactionTarget, inputDelay, processingDuration, presentationDelay,
  }));
});

After a week of data you usually see that a handful of elements cause the problem: a shop filter, an “add to cart” button, the mobile menu.

Break up long tasks

The most common cause of poor INP is a handler that does everything at once: updates state, recalculates a list, sends analytics. The browser cannot paint a frame until it finishes. The fix: do what the user sees right away and hand back the rest in pieces.

function yieldToMain() {
  if ('scheduler' in window && 'yield' in scheduler) return scheduler.yield();
  return new Promise((resolve) => setTimeout(resolve, 0));
}

button.addEventListener('click', async () => {
  showSpinner();            // the user sees a response immediately
  await yieldToMain();
  applyFilters();
  await yieldToMain();
  sendAnalytics();
});

Tip The Performance panel in Chrome DevTools now shows interactions on their own track and flags those longer than 200 ms. Record a click and look for the red triangles.

Quick wins

  • Defer third-party scripts (chat, heatmaps) until after interaction or idle time.
  • Shrink the DOM — thousands of nodes make every layout more expensive.
  • Use content-visibility: auto for sections below the fold.
  • Do not read dimensions (offsetHeight) right after changing styles in a loop — it forces synchronous layout.

INP is rarely fixed with a single change. But three or four targeted fixes in the most-clicked places are usually enough to get under 200 ms.

STOS

A blog about front-end, DevOps and tooling

Built on WordPress: custom block theme, Polylang, Yoast SEO. See the WordPress admin in Playground ↗

STOS is a fictional blog — a portfolio concept project. The author and content are samples.