A Practical Workflow for Fixing Slow INP
A page can paint quickly and still feel sluggish the moment someone taps a menu or picks a variant. Interaction to Next Paint (INP) is the Core Web Vital that captures that gap, and it is usually the hardest one to move because the cause lives in your JavaScript rather than in your server or images. Here is a workflow we would suggest for tracking down slow interactions and fixing them in a sensible order.
What the number actually means
INP looks at the responsiveness of interactions across a whole visit and reports a value at or near the slowest one. Google’s guidance treats 200 ms or less as good and anything above 500 ms as poor, judged at the 75th percentile of real page loads, with mobile and desktop reported separately. That percentile detail matters: one fast click on your laptop says very little about what a typical visitor on a mid-range phone experiences.
Every interaction is the sum of three stages:
- Input delay: the wait before your event handler can start, usually because the main thread is busy with something else.
- Processing time: how long your handlers take to run.
- Presentation delay: the time the browser needs to recalculate styles, lay out the page and paint the next frame.
Knowing which stage dominates tells you where to look. A long wait before the handler points at startup scripts or third-party code; slow handlers point at your own logic; a long tail after the handler points at DOM and layout work.
Start from field data, then reproduce
Begin with real-user numbers, either from the Chrome UX Report (shown at the top of PageSpeed Insights) or from your own monitoring. Then reproduce the slow action in the Performance panel of DevTools, ideally with CPU throttling switched on so your fast machine does not hide the problem. Look for long tasks on the main thread and check which stage of the interaction they sit in.
It also helps to know that the metric is now measurable beyond Chromium. web.dev announced in December 2025 that LCP and INP became Baseline Newly available after Safari 26.2 shipped support, which closes a blind spot for iOS visitors in your own analytics. The Chrome UX Report itself still draws only on eligible Chrome users, so do not expect it to describe Safari behaviour.
Fix 1: yield instead of hogging the thread
Long tasks, meaning anything over roughly 50 ms, are the most common culprit. When a click handler does everything in one go, the browser cannot paint until it finishes. Do the visible part first, then hand control back before the rest.
function yieldToMain() {
if (globalThis.scheduler && scheduler.yield) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function onAddToCart(event) {
showSpinner(event.currentTarget); // quick visual feedback
await yieldToMain(); // let the browser paint
await sendCartRequest();
await yieldToMain();
refreshCartCount(); // less urgent work last
}
web.dev’s roundup of Core Web Vitals fixes lists scheduler.yield() as available in Chromium browsers and in newer Firefox but not in Safari, so keep the setTimeout fallback shown above and check current support before dropping it.
Fix 2: stop forcing layout
Presentation delay often comes from mixing style writes with layout reads. Changing a class and then immediately reading offsetHeight makes the browser compute layout on the spot, and doing it in a loop multiplies the cost. Read everything first, write afterwards.
// Slow: read, write, read, write...
items.forEach((el) => {
el.style.width = el.parentElement.offsetWidth + 'px';
});
// Better: batch the reads, then the writes
const widths = items.map((el) => el.parentElement.offsetWidth);
items.forEach((el, i) => {
el.style.width = widths[i] + 'px';
});
Large DOMs make every one of these steps more expensive. Flattening deeply nested markup, or adding content only when it is needed, shrinks the work per interaction. For long off-screen sections, the CSS content-visibility property lets the browser skip rendering until they approach the viewport.
Where store themes usually hurt
Shopify’s own guidance on INP singles out a handful of repeat offenders: variant pickers that update price, images and button text all at once, cart drawers animated through width or top, mega menus that build a large DOM on hover, stylesheets split into hundreds of files, and analytics listeners that run synchronous code on every click. The remedies follow the same ideas as above: batch the DOM updates, animate with transform: translateX() instead of layout properties, keep menu markup in the page and toggle its visibility, and audit installed apps for scripts they still inject after removal.
If your storefront has accumulated several of these, a structured cleanup is quicker than patching one handler at a time. Our development and optimisation services cover exactly that kind of audit, and the themes we build keep their JavaScript deliberately small for the same reason.
A short checklist
- Find the worst interactions in field data before touching code.
- Identify which of the three stages dominates in a throttled profile.
- Do visible updates first, then yield before secondary work.
- Batch DOM reads and writes; animate with transforms.
- Remove or defer scripts that run on every click.
- Re-measure after each change rather than stacking fixes blindly.