FoundationsGENERALFRAMEWORK-SPECIFIC

The Frontend Reasoning Loop

Intent, event, state, logic, DOM, network, layout, paint, pixels, feedback — the chain every lesson in this domain is a zoom into.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

What is the general shape of every interaction, so I can ask the same questions of any feature?

The user intent

Someone clicks Save. That is the whole of what they want. Everything else in this domain is machinery in service of that expectation being met, quickly and honestly.

The obvious build

A click calls a handler, the handler updates state, the framework re-renders, and the UI is correct. If it is slow, the framework is slow.

Why it breaks

The handler does not run when the click happens; it runs when the main thread gets to it. If a task is already running, the delay is not the framework's (Interaction Responsiveness).

How it breaks in a real browser
  • The handler does not run when the click happens; it runs when the main thread gets to it. If a task is already running, the delay is not the framework's (Interaction Responsiveness).
  • "The framework re-renders" hides the entire question of what reached the DOM and what that cost the browser. Two renders producing identical output can differ by an order of magnitude in browser work (The Cost of a Change).
  • If the handler makes a request, the UI has at least four states, not two: idle, pending, succeeded, failed — and often a fifth, stale (Loading, Error, Empty — The States You Did Not Render).
  • If a second click happens before the first response, the responses can arrive in either order (Out-of-Order Responses).
  • Nothing in that description mentions whether the control was reachable by keyboard or whether the outcome was announced.
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • User intent. The person wants an outcome. They do not want a request, or a re-render, or a cache entry.
  • Browser event. Input becomes an event, dispatched through capture, target and bubble phases on the main thread (How an Event Is Dispatched).
  • Application state. Something changes. The first question is which kind of state it is and who owns it (Who Owns This State?).
  • UI logic. Your code decides what the interface should now look like, given that state.
  • DOM / rendering work. The difference between the previous and next descriptions reaches the DOM as mutations (Reconciliation and Keys).
  • Network / data. Some intents require the server. That introduces latency, failure, ordering and caching, all of which are now UI concerns (The Life of a Fetch).
  • Layout / paint / composite. The browser recomputes as much of the pipeline as the mutations invalidated (The Rendering Pipeline).
  • Pixels. A frame is produced. Not before the current task finishes.
  • User feedback. The person learns what happened — or does not, which is itself a design decision they will interpret.

What this makes the browser do

And which of it is avoidable.

  • Dispatching the event: hit-testing, building the propagation path, running listeners.
  • Whatever your handler does synchronously, on the thread that owes the user a frame.
  • Applying DOM mutations, invalidating style for affected subtrees, and re-running whichever pipeline stages the change touched.
  • Producing a frame — which can only happen between tasks, never during one (The Rendering Opportunity).

The loop, and the question at each step

Read this as a review checklist rather than a diagram to memorise. For any feature, walking the nine steps and answering each question out loud finds the gap faster than reading the code does.

Intent to feedback
if the server must agreeresponse updates statenext intentUser intentBrowser eventApplication stateUI logicDOM / rendering workNetwork / dataLayout / paint / compositePixelsUser feedback
UserLLMAgentToolDataDecisionHumanGuardrail
Nine steps, nine questions
  1. 1
    User intent

    What outcome does the person actually want?

    fails by Building the mechanism the ticket described instead of the outcome the person needed.

  2. 2
    Browser event

    What event, on what element, reachable how?

    fails by A handler on a non-interactive element, so it exists only for a mouse (Semantics Are Behaviour).

  3. 3
    Application state

    What changes, and who owns it?

    fails by Server data copied into component state, where it immediately begins to diverge (Server State Is Not Your State).

  4. 4
    UI logic

    What should the interface show for that state?

    fails by Only the success case is described, so pending and failure render as success.

  5. 5
    DOM work

    What actually reaches the DOM?

    fails by A whole list replaced because identity was not stable (Reconciliation and Keys).

  6. 6
    Network / data

    What does the server need to agree to?

    fails by No timeout, no cancellation, no ordering guarantee (Cancelling a Request Nobody Is Waiting For).

  7. 7
    Layout / paint / composite

    Which pipeline stages did this invalidate?

    fails by Animating a layout-triggering property every frame (Cheap and Expensive Animation).

  8. 8
    Pixels

    When can a frame actually be produced?

    fails by Never during the current task, which is why long handlers freeze the page (Long Tasks).

  9. 9
    User feedback

    How does the person learn what happened?

    fails by A visual-only change, unannounced, with focus left on a removed element (Live Regions and Announcement).

The same button, reviewed twice

The difference between the two versions below is not code quality. It is that the second one answers the questions the loop asks and the first one leaves four of them unanswered — and every unanswered question is a defect that will be reported as "sometimes it does not save".

Save
Answers three steps
<div class="btn" onclick="save()">Save</div>

async function save() {
  await fetch('/api/note', { method: 'POST', body })
  toast('Saved')
}
Answers all nine
<button type="submit">Save</button>

async function save() {
  if (pending) return           // no double submit
  setPending(true)              // visible + announced
  try {
    await fetch('/api/note', {
      method: 'POST', body,
      signal: controller.signal,  // cancellable
    })
    announce('Saved')            // not visual-only
  } catch (e) {
    if (e.name !== 'AbortError') showError(e)  // the branch that existed all along
  } finally {
    setPending(false)
  }
}

The first version is unreachable by keyboard, has no pending state so it can be submitted twice, has no failure branch so a failed save is indistinguishable from a successful one, and announces nothing. None of those are style problems; all four are reported by users as data loss.

How to build it

Most important first.

  • Ask the questions in order. Most bad frontend decisions are one of these steps answered without being noticed: state put in the wrong place, a request with no failure branch, a mutation that invalidates far more than intended.
  • Keep the synchronous part of a handler small. Acknowledge immediately; do the work in a way that leaves the thread free (Yielding and Scheduling).
  • Decide feedback deliberately. Every interaction that can take time or fail needs a visible, announced answer to "what happened" (Live Regions and Announcement).
  • Make the state question explicit before writing the component. Local, URL, server or persistent are four different answers with four different bug classes (The Seven Kinds of State).

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • Every step in this loop has an accessibility obligation, and the loop is the easiest way to notice them: is the trigger reachable and operable by keyboard, is the pending state announced, is the outcome announced, does focus end up somewhere sensible.
  • Feedback that exists only as a colour change or a position change conveys nothing to a screen-reader user and may convey nothing to a colour-blind user either (Contrast, Colour and Motion).
  • If the interaction removes or replaces the element that had focus, focus must be deliberately placed. Otherwise it falls to the document and the user loses their position entirely (Focus Management).
  • A long synchronous handler delays assistive-technology updates as surely as it delays pixels — the accessibility tree is computed on the same blocked thread (The Multi-Process Browser).

What can go wrong

Failure modes
  • The interaction has no pending state, so the user clicks again — and now there are two mutations in flight.
  • The failure branch does not exist, so a failed action looks exactly like a successful one until a refresh contradicts it.
  • Feedback is visual only, so it does not exist for a screen-reader user.
  • The handler does too much synchronously, so the interface freezes at exactly the moment the user is paying most attention to it.
What can arrive out of order
Security
  • The state change is a client-side belief. The mutation is the only thing that is real, and only the server can decide whether it was allowed (What the Frontend Is Responsible For in Auth).
  • Optimistic UI makes this sharper: the interface asserts an outcome before the authority has agreed. That is acceptable when rollback is honest, and dishonest when it is not (Optimistic UI).
  • Anything the handler renders from server data is a potential injection sink if it bypasses the framework's escaping (Cross-Site Scripting).
Misreads
  • "State changed, so the UI is correct." The UI is correct when the browser has painted it and the user has been told. Those are three separate events.
  • "The framework handles the middle." The framework handles the description-to-DOM step. Ownership, network, ordering, feedback and accessibility are yours.
  • "Speed is the framework's problem." The framework contributes; the main thread, the network and the amount of DOM you asked for contribute more.

Measuring it, and what changes in the field

How you would see this
  • Interaction latency in the field, measured from input to the next paint that reflects it — the metric that corresponds to what the user actually experienced (Interaction Responsiveness).
  • The Performance panel's interaction trace shows input delay, handler duration and presentation delay separately, which is the difference between "my code is slow" and "my code never got the thread" (Debugging Rendering and Jank).
  • Error and failure rates per interaction, not per page. A Save that fails 2% of the time is invisible in page-level telemetry (Frontend Error Tracking).
Slow device, slow network, large data, old tab
  • On a slow device, every step costs more and the handler is more likely to overlap the next input.
  • On a slow network, the gap between state change and confirmed outcome widens, which is exactly when pending and failure states stop being optional.
  • With a large dataset, the DOM-work step dominates and the rest becomes noise (List Virtualization).
What this costs
  • Thinking through all nine steps for every interaction is slower than writing the handler. It is also how you avoid discovering the missing failure branch in production.
  • Optimistic feedback improves perceived speed and costs you a rollback path and a reconciliation story (Rollback and Reconciliation).

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALThe chain holds regardless of framework, because every framework ultimately produces DOM mutations that the browser turns into pixels through the same pipeline.
  • FRAMEWORK-SPECIFICOnly the UI-logic and DOM-work steps differ meaningfully: React re-runs components and diffs a description, Solid and Svelte update the specific bindings that depend on the changed value, so "what reached the DOM" has different answers with identical output (Reactivity Models).

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

API Designerror-model
Performancepercentiles