INP Troubleshooting for Small Sites: Fixing Slow Buttons, Menus, and Form Interactions (2026)

Performance
Chrome DevTools performance panel highlighting slow interaction timing on a small website

INP—Interaction to Next Paint—became a Core Web Vital in March 2024, replacing First Input Delay. The change was not cosmetic. FID measured only the delay before the browser started processing the first interaction on a page. If your click handler took 800 milliseconds to execute but started immediately, FID said everything was fine. INP measures the full round trip: input event, processing time, and visual update. It captures every interaction during the visit—clicks, taps, key presses—and reports a value near the worst case. A sluggish dropdown menu that FID ignored now drags your INP score into the red.

For large JavaScript-heavy applications, INP is a serious engineering challenge. For small static sites with a few interactive elements, it is usually a solvable problem—but only if you know where to look and what patterns create the slowness in the first place.

What INP Actually Measures

Every time a user interacts with your page—clicking a button, tapping a menu toggle, pressing a key in a form field—the browser processes that event in three phases:

  1. Input delay. The time between the physical interaction and when the browser begins running your event handler. This is long when the main thread is already busy with something else—parsing a large script, running a timer callback, processing a previous event.

  2. Processing time. The time your event handler takes to execute. If your click handler queries the DOM extensively, recalculates styles, triggers layout, and then manipulates more elements, this phase stretches.

  3. Presentation delay. The time from when your handler finishes to when the browser paints the visual result. This is slow when your handler’s DOM changes trigger expensive layout recalculations or when the browser needs to composite many layers.

INP is the sum of these three phases, reported for an interaction near the worst observed during the page visit (technically the 98th percentile if there are 50+ interactions). Google considers anything under 200 milliseconds “good.” Between 200 and 500 milliseconds is “needs improvement.” Above 500 milliseconds is “poor.”

For context, 200 milliseconds is roughly the threshold of perceptible delay. Below it, interactions feel instant. Above it, users notice the lag—consciously or not.

Why Small Sites Get Caught Out

You might assume that a five-page static site with a navigation script, a contact form, and maybe a lightbox would not have INP problems. Often you are right. But several common patterns create surprising slowness:

Third-party scripts dominate the main thread. A chat widget, an analytics library with real-time tracking, a social media embed—each of these runs JavaScript on the main thread. When a user clicks your menu toggle, the browser might be mid-execution of an analytics callback. Your handler cannot start until that finishes. The input delay balloons.

jQuery or legacy libraries process inefficiently. Plenty of small sites still load jQuery for a handful of tasks. jQuery’s event delegation and DOM manipulation are not slow in isolation, but combined with plugins—a carousel library, a lightbox, a form validation suite—the chain of processing on each interaction can be substantial.

Layout thrashing in event handlers. Reading a layout property (like offsetHeight), then writing a style change, then reading again—this pattern forces the browser to recalculate layout on every read. A handler that does this in a loop can take tens of milliseconds per iteration, and it is incredibly common in custom animation code and accordion widgets.

Synchronous operations in click handlers. localStorage.setItem() is synchronous and can be slow. JSON.parse() on a large string blocks the thread. document.querySelectorAll() across a large DOM is not instant. These operations inside an event handler add directly to processing time.

Finding Slow Interactions in Chrome DevTools

You do not need specialised tooling. Chrome’s built-in DevTools are sufficient to identify and diagnose every INP problem on a small site.

The Performance Panel Method

  1. Open your page in Chrome. Open DevTools with F12.
  2. Go to the Performance tab.
  3. Click the gear icon and enable Web Vitals.
  4. Click the record button (or press Ctrl+E).
  5. Interact with the page. Click every button, open every menu, type in every form field, toggle every accordion. Interact with everything a real user would touch.
  6. Stop recording.

The timeline now shows your interactions as vertical markers in the Web Vitals lane. Each interaction is color-coded: green for good (under 200ms), yellow for needs improvement, red for poor. Click any interaction marker to see the breakdown: input delay, processing time, and presentation delay.

Look for red or yellow markers first. Expand the Main thread flame chart at the timestamp of a slow interaction. You will see exactly which functions executed during the processing phase. Long yellow or red blocks in the flame chart are your targets.

The Console API Method

For a quicker check, paste this into the Console before interacting:

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.entryType === 'event' && entry.duration > 100) {
      console.log(
        `Slow interaction: ${entry.name} on <${entry.target?.tagName}> — ${entry.duration}ms`,
        entry
      );
    }
  }
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });

Now interact with the page. Any interaction taking longer than 100 milliseconds logs to the console with the event type, target element, and total duration. This is a fast way to identify which specific interactions are slow without recording a full performance trace.

Chrome’s Built-in INP Debugger

As of 2026, Chrome DevTools includes an INP-specific overlay. In the Performance panel, look for the “Interactions” track. It shows every qualifying interaction with its total duration and a breakdown of the three phases. This is the most direct way to see your INP score in real time during testing.

Fixing Dropdown Menu Lag

Dropdown menus are one of the most common INP offenders on small sites. The user clicks a menu toggle, and the menu takes a perceptible moment to appear. The causes are predictable.

Heavy Toggle Logic

A menu toggle handler that does more than toggling a class or attribute is doing too much:

// Slow: queries DOM, forces layout, does multiple style changes
menuButton.addEventListener('click', () => {
  const menu = document.querySelector('.dropdown-menu');
  const menuHeight = menu.scrollHeight; // forces layout
  menu.style.height = menuHeight + 'px'; // triggers layout
  menu.style.opacity = '1';
  menu.classList.add('visible');
  document.body.style.overflow = 'hidden';
  // ... more DOM reads and writes interleaved
});
// Fast: toggles a single attribute, CSS handles the rest
menuButton.addEventListener('click', () => {
  const expanded = menuButton.getAttribute('aria-expanded') === 'true';
  menuButton.setAttribute('aria-expanded', String(!expanded));
});

Let CSS handle the visual transition. A single attribute change is a microsecond operation. The browser then applies the CSS rules and paints the change in the presentation phase, which is fast because there is no interleaved layout thrashing.

If you have built accessible dropdown menus using the Popover API, the browser handles the toggle natively with no JavaScript processing cost at all. The INP impact of a popover toggle is effectively zero.

Third-Party Scripts Blocking the Toggle

If your menu toggle feels slow but the handler itself is trivial, the problem is likely input delay—something else was running on the main thread when the user clicked. Record a performance trace and look at what occupies the main thread in the milliseconds before your handler fires.

Common culprits:

  • Analytics scripts processing a previous page event
  • Chat widgets polling or rendering
  • Ad scripts computing layout
  • Font loading callbacks executing

The fix depends on the culprit. Defer non-essential scripts with async or defer. Load chat widgets only after user interaction (click a “Chat with us” button to initialise, rather than loading on page load). Move analytics to requestIdleCallback or use a lightweight analytics solution that does not block the main thread.

Form Interaction Optimisation

Contact forms on static sites often use JavaScript for validation, and that validation code can cause INP issues when it runs synchronously on every keystroke or on submit.

Keystroke Validation

Real-time validation—checking the email format as the user types, showing character counts, validating phone number patterns—runs an event handler on every input or keyup event. If the handler is expensive (querying the DOM for error containers, reading and writing styles, running regex patterns), the rapid-fire events compound.

// Potentially slow on every keystroke
emailInput.addEventListener('input', () => {
  const value = emailInput.value;
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
  const errorEl = document.querySelector('.email-error');
  if (!isValid && value.length > 0) {
    errorEl.textContent = 'Please enter a valid email address';
    errorEl.style.display = 'block';
    emailInput.style.borderColor = 'red';
  } else {
    errorEl.textContent = '';
    errorEl.style.display = 'none';
    emailInput.style.borderColor = '';
  }
});

This is not catastrophically slow, but it is doing more work than necessary on every keystroke. Cache your DOM references outside the handler. Better still, debounce the validation so it runs only after the user pauses typing:

const emailInput = document.getElementById('email');
const errorEl = document.querySelector('.email-error');
let debounceTimer;

emailInput.addEventListener('input', () => {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(() => {
    const value = emailInput.value;
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    errorEl.textContent = (!isValid && value.length > 0)
      ? 'Please enter a valid email address'
      : '';
  }, 300);
});

Now the validation fires 300 milliseconds after the user stops typing, not on every keystroke. The intermediate keystrokes have near-zero processing time because they only clear and set a timer.

Submit Handler Weight

Form submission handlers that do synchronous validation of all fields, construct a payload, and then submit can be slow if the validation is heavy. Keep validation lightweight. Use the Constraint Validation API built into browsers—input.checkValidity() is native and fast:

form.addEventListener('submit', (e) => {
  if (!form.checkValidity()) {
    e.preventDefault();
    form.reportValidity(); // shows native browser validation messages
    return;
  }
  // proceed with submission
});

This uses the browser’s built-in validation engine, which is implemented in C++ and is dramatically faster than any JavaScript validation library.

Layout Thrashing: The Hidden INP Killer

Layout thrashing happens when JavaScript reads a layout property, writes a style change, then reads another layout property. Each read after a write forces the browser to recalculate layout synchronously. In isolation, one forced layout is fast. In a loop, it is devastating.

// Thrashing: forces layout on every iteration
items.forEach(item => {
  const height = item.offsetHeight;        // read → forces layout
  item.style.height = (height + 10) + 'px'; // write
});
// Batched: reads first, then writes
const heights = items.map(item => item.offsetHeight); // all reads
items.forEach((item, i) => {
  item.style.height = (heights[i] + 10) + 'px';       // all writes
});

The batched version forces layout once (on the first read), then writes all changes without forcing additional recalculations. The difference on a list of 20 elements can be 5ms versus 50ms—enough to push an interaction over the 200ms threshold when combined with other processing.

If you see purple “Layout” blocks repeated inside an event handler in the Performance panel flame chart, you have layout thrashing.

How DFM2HTML’s Approach Helps

DFM2HTML generates self-contained scripts that handle specific interactions without depending on heavy external libraries. The JavaScript handling documentation covers how the editor manages interactive elements—menu toggles, form validation, visual effects—using lean, purpose-built code rather than pulling in a monolithic library for a handful of features.

This matters for INP because every kilobyte of JavaScript is potential main-thread work. A 90KB library that you use 5% of still parses and compiles in its entirety. DFM2HTML’s approach of generating only the JavaScript needed for the specific interactions in your design keeps the total script weight low and the main thread clear.

The features overview details the specific interactive components available and how they are implemented. The generated code avoids the layout thrashing patterns described above and keeps event handlers minimal—toggle a class, update an attribute, let CSS handle the visual transition.

A Systematic INP Debugging Checklist

When INP is flagged on your static site, work through this list:

  1. Identify the slow interaction. Use the Performance Observer snippet or the Interactions track in DevTools to find which specific interaction is slow.
  2. Record a performance trace of that interaction. Look at the flame chart at the timestamp of the interaction.
  3. Check input delay. If there is a long gap before your handler starts, something else was running. Find it and defer it.
  4. Check processing time. If your handler is the long block, look for layout thrashing, heavy DOM queries, or synchronous operations inside it.
  5. Check presentation delay. If processing finishes quickly but the paint is delayed, your DOM changes may be triggering expensive compositing. Simplify the visual change.
  6. Test on a throttled CPU. In DevTools Performance settings, set CPU throttling to 4x or 6x slowdown. This simulates a mid-range phone and reveals issues that are invisible on your development machine.

Most small-site INP problems resolve at step 3 (defer a third-party script) or step 4 (simplify the event handler). Steps 5 and 6 catch edge cases.

What Good INP Looks Like on a Small Site

A well-optimised small static site should have INP well under 100 milliseconds. Menu toggles under 50ms. Form interactions under 80ms. Button clicks under 30ms. These numbers leave generous headroom below the 200ms threshold and ensure that interactions feel instant even on slower devices.

If you are working through Core Web Vitals optimisation and INP is the metric that needs attention, the approach is different from LCP or CLS fixes. Those metrics are about assets and layout. INP is about code execution. The fix is always in the JavaScript—either your JavaScript or someone else’s that runs on your page.

Find the slow interaction, profile it, fix the handler or defer the blocker, and re-measure. On a small site, the entire process rarely takes more than an hour. The tutorials index covers the broader performance picture if you want to work through LCP and CLS fixes alongside your INP improvements.


← Back to Tutorials Download DFM2HTML