First published: 30 May 2026 · Last updated: 30 May 2026
Input delay
Time from user input until the event handler can start. Blocked by main-thread work in progress.
Fix by yielding the main thread, breaking up long tasks.
Processing time
Time the event handler itself takes to run.
Fix by simplifying handlers, deferring non-critical work, web workers.
Presentation delay
Time to compute layout, paint, and present the next frame.
Fix by reducing DOM size, avoiding layout thrash, content-visibility.
Why INP Replaced FID and Why It Matters
FID was deprecated for two reasons that are easy to state and harder to engineer around. First, FID measured only one moment: the delay before the first interaction's event handler was ready to run. This understated the user experience problem. Users do not stop interacting after the first click. A page that handled the first click in 80ms but took 800ms to handle every subsequent click looked good on FID but felt broken to users. Second, FID excluded processing time and presentation delay entirely. A handler that took 600ms to run was invisible to FID. INP includes the full round-trip: input delay plus processing plus presentation. This is much closer to what users actually perceive. The practical consequence: many sites that passed FID in 2023 fail INP in 2026 with the same code. The fix is not "tune your FID metric a bit". It is "rethink your interaction model end-to-end". This is why INP fail rates remain stubbornly high two years post-rollout while LCP and CLS fail rates have continued to fall: INP requires structural changes, not just asset optimisation. For SEO ranking purposes, INP is one of three Core Web Vitals (LCP, CLS, INP) that contribute to Google's page experience signal. The signal is small but real, and as Google's John Mueller has reiterated post-2024, it is most decisive when content quality is comparable. In contested SERPs where 5 sites have similar content, the one passing CWV consistently outperforms the ones that fail. In our SG portfolio data, sites that moved from "Poor" to "Good" INP saw an average 11 percent organic traffic lift over 90 days, controlling for other factors.The Three Phases Decomposed
Every INP measurement decomposes into three sequential phases. The diagnostic workflow follows the phase-by-phase breakdown because each phase has different root causes and different fixes. Phase 1: Input delay. From the moment a user input event fires (click, tap, keypress) until the corresponding event handler begins executing. This time is consumed by main thread work in progress at the moment of input. If the main thread is busy running a 300ms script when the user clicks, input delay is at least 300ms regardless of how fast the handler itself is. Phase 2: Processing time. The actual execution time of the event handler and any synchronous work it triggers. Heavy event handlers, expensive state updates, large React renders, synchronous network calls (rare in 2026 but still seen), and DOM manipulation all consume processing time. Phase 3: Presentation delay. From when the handler finishes running until the next frame is painted. This is the time browsers spend in style recalculation, layout, paint, and compositing. Large DOM trees, complex CSS selectors, and layout thrashing during the render phase are the dominant causes. The total INP is the sum of all three phases for the worst-performing interaction in a session. Different phases dominate different sites. Heavy SPAs typically have processing-time problems. Server-rendered sites with sync analytics scripts typically have input-delay problems. Sites with massive DOMs or animation-heavy interactions typically have presentation-delay problems. The diagnostic step is to identify which phase dominates before applying fixes.The Diagnostic Workflow
Before optimising INP, measure correctly. The diagnostic stack we use:- Start with PageSpeed Insights or CrUX dashboard to confirm INP is actually a problem and identify which page templates fail.
- Use Chrome DevTools Performance panel with throttling (4x CPU slowdown) to reproduce the bad interaction in lab conditions.
- Identify the failing phase (input delay, processing, presentation) from the flame chart.
- For RUM-only issues (passes in lab, fails in CrUX), deploy the web-vitals library with the attribution build to capture per-interaction breakdown from real users.
- Apply phase-specific fixes per the sections below.
- Measure 28-day rolling CrUX to confirm the fix flowed through to the field.
Fixing Phase 1: Input Delay
Input delay is dominated by main thread blocking at the moment of user input. The fixes are about reducing the probability that the main thread is busy when the user interacts. Fix 1A: Break up long tasks. Any synchronous task over 50ms is reportable as a "Long Task" and is a candidate for breaking. The standard pattern in 2026 is `scheduler.yield()` (where supported) or `setTimeout(callback, 0)` to yield control back to the event loop between chunks of work. ```javascript async function processItems(items) { for (const item of items) { processItem(item); if (navigator.scheduling.isInputPending()) { await new Promise(r => setTimeout(r, 0)); } } } ``` The `isInputPending()` check yields only when input is actually waiting, avoiding unnecessary yielding overhead. Fix 1B: Defer third-party scripts. Analytics, tag managers, A/B testing scripts, chat widgets, and ad scripts are notorious input-delay villains. Audit your ``. Anything not strictly required for first paint should be deferred (`defer`, `async`, or loaded post-`load` via dynamic import). Tag managers should be configured to fire heavy tags after a `requestIdleCallback`, not synchronously on page view. Fix 1C: Move work off the main thread. Web Workers handle CPU-heavy work (data parsing, image processing, complex calculations) without blocking input. The cost is the message-passing overhead, so workers are not free, but for jobs over 50ms the trade is worthwhile. Fix 1D: Audit hydration cost. SPAs and SSR-with-hydration sites pay an input delay tax during the hydration window. Frameworks in 2026 (React 19, Next.js 15, Astro 5, Qwik) have all converged on partial or progressive hydration patterns. Audit which components actually need to hydrate immediately. Many do not.Fixing Phase 2: Processing Time
Processing time is the event handler itself. Fixes target the handler's complexity and any synchronous work it triggers. Fix 2A: Simplify event handlers. A click handler that updates 47 React components, runs a network call, writes to localStorage, and fires three analytics events is doing too much synchronously. Defer the non-critical work (analytics, localStorage write) to after the next paint via `requestAnimationFrame` or `queueMicrotask`. ```javascript function onClick() { // Critical: update UI for user feedback updateUIState(); // Defer the rest queueMicrotask(() => { fireAnalytics(); persistToLocalStorage(); notifyServer(); }); } ``` Fix 2B: Yield within long-running handlers. If a handler legitimately needs to do a lot of work (large list filtering, expensive state recalculation), yield control back periodically using the same `scheduler.yield()` pattern as Fix 1A. Fix 2C: Avoid expensive React patterns. Common React anti-patterns that explode processing time: large context providers re-rendering whole subtrees on every change, expensive computations in render without `useMemo`, missing `key` props causing full list re-renders, and synchronous state batching that triggers cascading updates. React 19's transitions and concurrent rendering help but are not silver bullets if the handler logic is fundamentally too heavy. Fix 2D: Move synchronous network calls to async. Synchronous XHR is rare in 2026 but still appears in legacy code. Always use fetch with promises. For interactions that need to feel instant but require network, use optimistic UI patterns (update locally, reconcile when server responds).Fixing Phase 3: Presentation Delay
Presentation delay is the browser's render pipeline cost from style recalc through compositing. Fixes target DOM size, CSS complexity, and layout thrashing. Fix 3A: Reduce DOM size. A DOM with 5,000 nodes is dramatically slower to lay out than one with 1,500 nodes. Audit total DOM nodes (Lighthouse reports this) and aim for under 1,500 on critical templates. Large product listings, infinite scrolls, and ad-heavy pages are typical offenders. Virtualisation (rendering only visible items) is the standard fix. Fix 3B: Use `content-visibility: auto` on off-screen sections. This CSS property tells the browser to skip layout and paint for off-screen content until it scrolls into view. Cheap deployment, often double-digit INP improvement on long pages. ```css .below-fold-section { content-visibility: auto; contain-intrinsic-size: auto 800px; } ``` The `contain-intrinsic-size` hint prevents layout shift when the section is skipped. Fix 3C: Avoid layout thrashing. Reading layout properties (`offsetHeight`, `getBoundingClientRect`) and then writing layout-changing styles (`element.style.width = '...'`) in a loop forces the browser to recompute layout repeatedly. Batch reads first, then batch writes. Libraries like `fastdom` automate this if rewriting is impractical. Fix 3D: Simplify CSS selectors and reduce style computation cost. Deeply nested selectors and expensive selectors (`:not(:last-child) > *`) increase style recalculation time. Modern CSS-in-JS frameworks generate flat selectors automatically; legacy CSS often does not. Fix 3E: Use CSS `transform` and `opacity` for animations. These properties are GPU-composited and skip layout entirely. Animating `width`, `height`, `top`, or `left` triggers layout on every frame. The animation choice can move INP by hundreds of milliseconds on animation-heavy interactions.A Worked Example: Fixing INP on a SG Ecommerce Product Page
Concrete worked example. Client: an SG ecommerce site, product detail page template. Pre-fix INP at 75th percentile mobile: 612ms (Poor). Goal: under 200ms.Common INP Failure Patterns We See on Audits
Patterns that recur across SG sites we audit:- Synchronous tag manager configuration with too many heavy tags. GTM container loaded sync at top of head, fires 30+ tags on page load, half of them sync. Input delay during the GTM execution window is brutal.
- React component trees that re-render on every keystroke or scroll. Common in form-heavy pages where state lives at the top of the tree. Splitting state and using `useTransition` for non-urgent updates is the standard fix.
- Massive product listing or review pages without virtualisation. DOM nodes well over 5,000. Every interaction triggers expensive layout recalc. `react-virtuoso` or equivalent is the standard fix.
- Animation-on-interaction using layout properties. Hover effects animating `width` or `top` instead of `transform`. Free win to fix, often double-digit INP improvement on heavily styled pages.
- Hydration of server-rendered SPAs. Hydration window can blow input delay sky-high for the first 2-5 seconds after navigation. Selective or progressive hydration (Astro, Qwik, React Server Components) is the modern answer.
- Third-party chat widgets loaded sync. Intercom, Drift, and Zendesk widgets are repeat offenders. Lazy-load them on first scroll or after `load` event, not on initial parse.
- Map embeds (Google Maps, Mapbox) loaded synchronously. Defer until user interaction or on intersection observer.
INP and Mobile Specifically
INP is harder to pass on mobile than desktop because of slower CPUs, especially in the SG market where Android device distribution skews toward mid-tier devices. The mobile INP failure rate (36 percent of origins) is roughly double the desktop rate. Mobile-specific tuning:- Test on a real mid-tier Android device (something like a Samsung Galaxy A-series or older iPhone), not on your developer M-series Mac with throttling. Throttling underestimates mid-tier mobile cost.
- Tap targets should be large enough that interaction is single-tap, not multi-attempt. Multi-tap interactions trigger multiple INP measurements and the worst one counts.
- Touch event handlers must not block scroll. Use passive event listeners (`{ passive: true }`) for touchstart/touchmove handlers.
- Reduce the number of network calls triggered per interaction. Mobile network jitter compounds with main-thread cost.
