First Input Delay (FID)

TL;DR / FID measures the time between a user's first discrete interaction (click, tap, key press) and the moment the browser can begin processing the event handler, capturing main-thread blocking during the critical loading phase.

How It Works

 ┌──────────┐        ┌──────────────┐        ┌──────────────┐        ┌────────────┐
 │   User   │        │    Input     │        │ Main Thread  │        │   Event    │
 │  Clicks  │───────→│ Event Queued │───────→│  Busy (JS)   │───────→│ Processed  │
 └──────────┘        └──────────────┘        └──────────────┘        └────────────┘


                     |---------- FID ----------|
                 input delay only (first interaction)

Edit diagram

First Input Delay is one of Google's Core Web Vitals, measuring the input delay component of the first user interaction. It captures a specific slice of latency: the time the input event sits in the browser's task queue waiting for the main thread to become available. FID does not include the time spent running your event handler or producing the next visual frame -- it is purely the wait time.

The metric only triggers on discrete input events: clicks, taps, and key presses. Continuous events like scroll and mousemove are excluded because they are handled on the compositor thread and have different latency characteristics. The first qualifying interaction during the page lifecycle produces the FID value. There is no second measurement -- it is explicitly a first-impression metric.

FID exists because the loading phase is when the main thread is most likely to be blocked. Bundle evaluation, hydration, third-party script initialization, and JSON parsing all generate long tasks that prevent the browser from responding to input. A user who clicks a button 2 seconds into page load may find the click event queued for 300ms while a script evaluates. That 300ms queue time is the FID value.

Measure FID with PerformanceObserver: observe type: 'first-input' with buffered: true. The entry provides startTime (when the input event was received by the browser), processingStart (when the event handler began executing), and processingEnd. FID is processingStart - startTime. The buffered flag is essential since the first input almost always occurs before your analytics script loads.

Google's thresholds: Good is under 100ms, Needs Improvement is 100-300ms, and Poor is above 300ms. The 75th percentile of page loads over a 28-day window determines the field assessment in Chrome User Experience Report (CrUX) data.

FID correlates strongly with Total Blocking Time (TBT), the lab metric that sums the excess blocking time (above 50ms per task) of all long tasks between First Contentful Paint and Time to Interactive. TBT cannot be measured in the field (it requires knowing TTI, which needs a quiet window detection), but it serves as the lab proxy for FID optimization.

The primary optimization strategies target main-thread availability during load. Code splitting ensures only critical JavaScript loads initially. Script defer and async attributes prevent parser-blocking evaluation. Lazy hydration delays component hydration until interaction or viewport entry. Web Workers move expensive computation (data processing, search indexing) off the main thread entirely. requestIdleCallback defers non-critical initialization to idle periods.

Third-party scripts are frequent FID offenders. Tag managers, analytics, ad scripts, and chat widgets execute during load and are often synchronous. Audit third-party impact with DevTools' "Third-party" filter in the Network panel or the Performance panel's "Third-party badges." Loading these scripts with async, defer, or dynamic injection after user interaction reduces their FID impact.

FID's limitation is fundamental: it only measures the first interaction. A page that becomes janky 10 seconds after load (due to a deferred heavy operation) shows excellent FID despite terrible responsiveness. This is precisely why Interaction to Next Paint (INP) has replaced FID as the responsiveness Core Web Vital since March 2024. INP measures all interactions throughout the page lifecycle.

FID also ignores processing time and presentation delay. A page could have 0ms FID but 500ms of event handler execution, resulting in a 500ms delay before the visual update. The user experiences this as a 500ms lag, but FID reports 0ms. INP captures the full duration from input to next paint, making it the more complete responsiveness metric.

Despite being superseded, FID remains widely reported in CrUX and analytics tools. Understanding it helps interpret historical data and optimize for the loading-phase responsiveness that FID specifically targets.

Gotchas

  • FID only measures the first interaction -- subsequent interactions can be arbitrarily slow and FID will still report a great score; use INP for full-session responsiveness
  • FID cannot be measured in lab tools -- Lighthouse, WebPageTest, and DevTools cannot simulate real user input timing; TBT is the lab proxy metric
  • FID excludes scroll and hover -- these events are handled by the compositor thread and do not reflect main-thread blocking; only discrete events (click, tap, keydown) qualify
  • A 0ms FID does not mean the interaction was fast -- it means the main thread was idle when the event arrived; the actual event handler could take seconds, and FID would still report 0ms
  • buffered: true is mandatory for FID observation -- the first input almost always happens before your PerformanceObserver is registered; without buffering, you miss the entry entirely