Problem says X → think Y
The searchable index of this domain. The left column is what the problem sounds like when a designer, a support ticket or a user describes it; the right column is the thing to think before you start typing.
91 of 91 rows
| The problem says | Think |
|---|---|
| The click registers but the UI does not change for an uncomfortable moment | Interaction responsiveness, not "the network". Between the input event and the next frame the main thread has to run your handler, apply the state change, re-render and let the browser lay out and paint; anything already queued on that thread runs first.Interaction Responsiveness → |
| Content jumps down the page after it has already appeared | Visual stability. Something occupied zero space and then occupied real space — an unsized image, a late banner, a font swapping to different metrics. Reserve the box before the content arrives rather than animating the correction.Visual Stability → |
| The screen is blank or skeletal long after the request finished | Follow the critical path, not the total transfer size. Something between the HTML and the first meaningful paint is serialised: a blocking stylesheet, a script that must parse before anything renders, or a data request that could not start until a bundle had executed.The Critical Rendering Path → |
| The JavaScript bundle is enormous and every route pays for all of it | Split by route and by interaction, then check what tree shaking could not remove. Bundle size is a proxy: the cost that matters is parse, compile and execute on a mid-range phone, which scales with what actually runs.Code Splitting → |
| Four components mount and all four request the same endpoint | Deduplication in a shared cache keyed by the request, so concurrent callers join one in-flight promise. Without it, every component that "just fetches what it needs" multiplies the load by the number of components.Five Components, One Request → |
| Typing in the search box shows results for a query the user already replaced | A response race. Requests are independent and complete out of order, so the last response to arrive is not the last one issued. Cancel superseded requests and discard any response whose key is no longer the current one.Out-of-Order Responses → |
| The interface should feel instant even though the write takes a round trip | Optimistic UI: apply the predicted result to the cache immediately, keep the previous value, and reconcile against what the server actually returns. The hard half is the rollback and the message you show when the prediction was wrong.Optimistic UI → |
| The server-rendered page looks finished but ignores clicks for a while | Hydration. The markup arrived without its behaviour; the framework must download, parse and execute the bundle and re-establish the component tree before any listener exists. Pixels are not interactivity.Hydration → |
| A loop over elements reads offsetHeight and then sets a style, over and over | Layout thrashing. Each read after a write forces the browser to recompute geometry synchronously, so an O(n) loop becomes n forced layouts. Batch all reads, then all writes.Layout Thrashing → |
| The table has a hundred thousand rows and scrolling is unusable | Virtualization: render only the rows in and near the viewport and translate a spacer for the rest. The cost is that find-in-page, anchor links, keyboard navigation and screen-reader row counts all need explicit handling.List Virtualization → |
| A heavy computation runs on every input and the page stops responding | Move it off the thread that owns the DOM. A worker runs in parallel and cannot touch the DOM, so the boundary is a message — which means the data has to be worth the cost of copying or transferring it.When a Worker Is Actually the Answer → |
| A user wants to send a colleague a link to exactly the filters they are looking at | The URL is the state. Anything that describes what is on screen and should survive reload, back, bookmark or share belongs in the path or the query string, not in a component.The URL Is Application State → |
| The app has to keep working on a train with no signal | A service worker to answer requests from a cache, plus local persistence for anything the user creates while offline. The design problem is not the cache; it is the queue of mutations and what happens when they finally reach a server that has moved on.Caching Strategies → |
| The product wants to render HTML that came from a user or a CMS | This is an XSS sink. Escape by default and only sanitize with a maintained, allow-list library at the moment of insertion; a regex over the string is not sanitization, and neither is trusting the source.Sanitization and Trusted HTML → |
| The console says the response was blocked by cross-origin policy | The request usually happened; the browser refused to let this origin read the response. CORS is a browser response-access policy, so the fix belongs in the other origin's response headers — and it is not what protects that server.CORS → |
| A tab left open for two days broke after the API changed | Web clients never update atomically. Old bundles keep calling old endpoints; assume version skew and make the contract additive, with a way for the client to notice it is stale.Long-Lived Clients and Version Skew → |
| A clickable div works with a mouse and does nothing with a keyboard | The element carries no role, no tab stop and no default activation. Use the real control; ARIA can rename a thing but it cannot give a div a button's behaviour.Semantics Before ARIA → |
| The screen reader announces "clickable, blank" | The accessibility tree is computed from your semantics. No accessible name means nothing to announce — a label, the text content or an aria-label has to exist before the node means anything.The Accessibility Tree → |
| Every element is a div with a utility class and nobody can find anything | Div soup costs you the platform: no landmarks, no heading outline, no default keyboard behaviour, and no free semantics for tests or assistive technology. Structure is a functional requirement, not a stylistic one.Div Soup: How It Happens and What It Costs → |
| Appending a thousand nodes one at a time is slow | Each mutation invalidates style and can force layout if something reads geometry between them. Build off-document or in one insertion so the browser does the work once.What a Mutation Costs → |
| A list re-orders and the wrong rows keep the wrong input values | Keys establish node identity across renders. Index keys tell the reconciler that position is identity, so state and DOM nodes follow the position rather than the item.Reconciliation and Keys → |
| A style that should obviously win does not | Resolution order is a specific algorithm: origin and importance, then layer, then specificity, then document order. Reach for the cascade before reaching for !important, which only moves the fight.The Cascade → |
| Theming requires rebuilding the CSS for every variant | Custom properties are resolved at computed-value time and inherit, so one declaration on a scope re-themes the subtree without a second stylesheet or a class explosion.Custom Properties → |
| Style recalculation shows up as a large slice of the profile | Selector matching cost scales with how many elements are invalidated, not with how clever the selector is. Look at what invalidated the subtree before you rewrite the selectors.Style Invalidation → |
| An animation stutters even though the code looks trivial | Ask which pipeline stages the animated property invalidates. Animating a geometric property runs layout every frame; transform and opacity can often stay on the compositor — but only when the element is genuinely on its own layer.Cheap and Expensive Animation → |
| Someone added will-change everywhere and it got worse | Every promoted layer costs memory and compositing work, and too many of them push the page into layer explosion. Promotion is a targeted fix with a measurable cost, not a global setting.Layer Explosion → |
| The page cannot even show a spinner while it is working | A task runs to completion before the browser gets a rendering opportunity. Your synchronous work and the frame you want to paint are competing for the same thread; nothing paints until you yield.The Rendering Opportunity → |
| Awaiting a promise still froze the UI | Microtasks drain within the same task, before the next rendering opportunity. Splitting synchronous work across promise callbacks does not yield to the browser — a real task boundary or an explicit scheduler does.The Microtask Checkpoint → |
| A single function occupies the main thread long enough to be visible | Break the work into chunks and yield between them so input and rendering can interleave. The point is not to make the total faster; it is to stop it being one indivisible block of unresponsiveness.Yielding and Scheduling → |
| Scrolling feels heavy on touch devices | A non-passive touch or wheel listener means the compositor must wait to find out whether you will preventDefault. Mark listeners passive unless you genuinely cancel the default action.Passive Listeners → |
| Sending a large object to a worker is slower than doing the work inline | Structured clone copies; the copy is proportional to the payload and happens on both threads. Transferables move ownership of a buffer instead, which is what makes worker offloading pay for large data.Structured Clone and Transferables → |
| Hundreds of rows each attach their own click listener | Delegate to a common ancestor and use the event target. Dispatch already walks the tree for you, so one listener is enough — and rows added later work without re-binding.Event Delegation → |
| preventDefault fixed one thing and broke form submission or middle-click | Default actions are the browser's behaviour, not decoration: submission, navigation, scrolling, text selection, context menus. Cancel the specific one you mean, on the specific event you mean.preventDefault vs stopPropagation → |
| A modal opens and the keyboard is still in the page behind it | Focus management is part of the component contract: move focus in, constrain it while open, restore it to the trigger on close, and make Escape work.Focus Management → |
| Validation errors appear in red and a screen-reader user hears nothing | Colour is not an announcement. Associate the message with the field, mark the field invalid, and put the summary somewhere focus or a live region will reach.Errors People Can Actually Perceive → |
| A custom form rebuilt everything the browser already does | Native forms bring submission, validation, autofill, mobile keyboards, labels and reset for free — and they work before your JavaScript loads. Start there and add only what is missing.Native Forms First → |
| A toast appears and assistive technology never mentions it | A live region announces changes inside a container that already existed when the page settled. Inserting the region and the message together often announces nothing.Live Regions and Announcement → |
| The design relies on a hover state to reveal an action | Hover is not available to keyboard, touch or voice users. Any state reachable by pointer alone needs an equivalent that focus or activation can reach.Keyboard Operability → |
| The component looks right in the page and wrong in a sidebar | It is responding to the viewport when it should respond to its container. Container queries let a component adapt to the space it was actually given, which is what makes it reusable.Container Queries → |
| A hero image is downloaded at desktop resolution on a phone | Give the browser the information to choose: intrinsic widths, a sizes hint and modern formats. The preload scanner picks the candidate early, long before your layout code runs.Responsive Images → |
| The same value is stored in three places and they drift apart | Only one of them is state; the others are derived. Compute what can be computed and give the rest a single owner, or you will keep writing synchronisation code that fixes the symptom.Derived State → |
| A value is passed through five components that do not use it | Prop drilling is a signal about boundaries. Context and a global store are two different fixes with different costs — context re-renders consumers, and a global store makes ownership everyone's problem.Prop Drilling, Context and Global State → |
| Cached server data is being edited in a global store as if it were local | Server state is a copy of something you do not own: it goes stale, it needs revalidation, it can conflict. Client state is yours and never goes stale. Conflating them is how you get a store full of things you have to invalidate manually.Server State Is Not Your State → |
| Back returns to the right route but the wrong scroll position | Client-side routing replaced navigation, so the browser's scroll restoration no longer applies. Restoring position per history entry is now your job, and it interacts with async content that has not arrived yet.Scroll Restoration → |
| Every navigation shows a full-page spinner | The loading boundary is too high. Put it around the part that is actually pending so the shell, the navigation and the already-loaded content stay on screen.Route Loading Boundaries → |
| Nobody can say what happens when the request fails | Loading and error are not two booleans, they are states with content: retryable or not, partial or empty, stale-but-visible or gone. Design them as deliberately as the success case.Loading, Error, Empty — The States You Did Not Render → |
| After a mutation the list still shows the old data | The write succeeded and nothing invalidated the read. Query keys are the contract between a mutation and the caches it makes stale, and they only work if the keys are structured deliberately.Query Keys and Invalidation → |
| Data is either always stale or always refetching | Stale-while-revalidate separates "what do I show now" from "when do I check again". Serve the cached value immediately, refresh in the background, and decide per query how much staleness the screen can tolerate.Stale-While-Revalidate → |
| A live feed reconnects in a tight loop after the server restarts | Every client retried at the same interval and re-created the outage. Exponential backoff with jitter and a cap, plus a bounded attempt count and a visible degraded state.Reconnect and Backoff → |
| The realtime UI drifts from the server after a dropped connection | Live events are a delta stream; a gap in it is unrecoverable by replay alone. Resynchronise by refetching authoritative state on reconnect and treating events as an optimisation over it.Resynchronisation After a Gap → |
| The same event is delivered twice and the counter increments twice | At-least-once delivery is the normal case for realtime transports. Give events an id, keep a recent-id set, and make application of an event idempotent.Ordering and Duplicate Delivery → |
| The session expired and the user lost a half-written form | Expiry is a UI state, not an exception. Detect it on the response, preserve what the user typed, re-authenticate, and resume — rather than redirecting to a login page that discards everything.Session Expiry and the Refresh Race → |
| The admin button is hidden, so the feature is protected | Hiding is presentation. The bundle is public, the route is reachable and the request can be replayed by hand; the server is the only place authorization exists.Authorization-Aware UI → |
| A user logs out in one tab and stays logged in in another | Tabs share an origin's storage and cookies but not their memory. Auth changes need a cross-tab channel or a storage event, and each tab has to react rather than assume.Auth Across Tabs → |
| Someone asks where to keep the access token | The trade is explicit: a cookie the script cannot read removes the XSS exfiltration path but is sent automatically, so it needs CSRF defence; a script-readable token avoids that but is readable by any script that gets in.Cookies vs Script-Readable Tokens → |
| A state-changing POST works from any origin that can make the user's browser send it | CSRF exists because the browser attaches cookies to cross-site requests on its own. SameSite plus a token the attacker cannot read, and never a GET that changes state.Cross-Site Request Forgery → |
| A third-party tag was added to every page by a tag manager | Same-origin script runs with your origin's full authority: your DOM, your cookies, your tokens. Inventory what you load, control it at the boundary, and treat a vendor update as a deploy to your site.Third-Party Scripts and the Supply Chain → |
| The team wants defence in depth against injected script | A content security policy limits which sources can execute, so a successful injection has nowhere to load from. It is a mitigation layered on top of escaping, not a replacement for it.Content Security Policy → |
| The site can be framed and clicks land on something invisible | Clickjacking overlays your real UI under an attacker's page. Frame-ancestors in a policy tells the browser to refuse, which is one of the defences only the browser can enforce.Clickjacking and Framing → |
| Someone reaches for localStorage by default | It is synchronous, string-only, small, script-readable and shared across the whole origin. Choose from lifetime, size, synchronicity and exposure — cookies, web storage, IndexedDB and Cache Storage are four different answers.Choosing Browser Storage → |
| Reading stored data on startup blocks the first paint | Web storage is synchronous on the main thread, so a large read stalls everything. Structured or large data belongs in IndexedDB, which is asynchronous and indexed.IndexedDB → |
| A deploy went out and users kept getting the old assets | A service worker is a programmable proxy with its own lifecycle. Install, waiting and activate decide when the new version takes over, and a page keeps talking to the worker that controlled it.The Service Worker Lifecycle → |
| Actions taken offline vanish or apply twice when connectivity returns | You need a durable queue with ids, ordering and a conflict policy. Replay is a distributed-systems problem that happens to live in the browser.The Offline Mutation Queue → |
| Somebody wants to start optimising | Get the evidence first: which of loading, responsiveness or stability is actually bad, on which devices, on which route. Optimising without that is how teams spend a sprint improving a number nobody feels.Measure Before Optimising → |
| Requests appear one after another instead of together | A waterfall means each request had to wait for the one before to reveal it. Look for discovery that depends on execution — a bundle that must run before a fetch can be issued.Reading a Network Waterfall → |
| A stylesheet or a synchronous script in the head delays every paint | Render-blocking resources are blocking by design: the browser cannot paint with unresolved styles or run a parser-blocking script later. Inline what the first screen needs and defer the rest.Render-Blocking Resources → |
| The team keeps adding preconnect and preload to everything | Resource hints spend bandwidth and connection budget on your guess. A wrong hint competes with the resource that was actually on the critical path.Resource Hints → |
| A cached asset will not update after a deploy | Long-lived immutable caching only works with content-hashed filenames: a new build means a new URL, so nothing has to expire.Content-Hashed Assets → |
| Text is invisible while a web font downloads | The font loading strategy decides whether text blocks, swaps or falls back — and the swap is the source of the reflow. Preload the face the first screen needs and match fallback metrics.Images and Fonts → |
| Tree shaking removed nothing | Elimination needs statically analysable ESM and no side effects the bundler must assume. A CommonJS dependency or an unmarked side effect makes the whole module unremovable.Tree Shaking → |
| A single import pulled in a whole library | Read the module graph before blaming the bundler. Barrel files, dynamic member access and a package with one entry point all defeat splitting.Bundle Analysis → |
| A modern syntax feature broke on an older browser | Down-levelling syntax and supplying a missing runtime API are two different jobs; a build that transpiles will not add a method that does not exist.Polyfills vs Transpilation → |
| Production errors point at line one of a minified file | Source maps map generated positions back to source. Upload them to the error tracker rather than serving them publicly, and hash them like everything else.Source Maps → |
| Memoization was added everywhere and nothing got faster | It trades memory and comparison cost against re-render cost. If the props change every render, or the render was cheap, you added work — and a dependency array is now something you can get wrong.Memoization → |
| The app gets slower the longer the tab stays open | Retention: listeners, timers, observers, closures over DOM nodes and caches that never evict. Heap snapshots across navigations show what survived that should not have.Memory Leaks → |
| Server-rendered markup does not match what the client renders | A hydration mismatch. Something differed between environments — a date, a random value, a browser-only branch — and the framework either patches it expensively or discards the server output.Hydration Mismatch → |
| The page waits on one slow query before anything renders | Streaming SSR lets the shell flush while the slow part resolves, so the browser starts on the critical path immediately instead of waiting for the whole document.Streaming Server Rendering → |
| A content page ships a full application bundle for one interactive widget | Islands: server-render everything, hydrate only the interactive parts. The cost is a build and routing model that can express partial hydration and share state across it.Islands and Partial Hydration → |
| Nobody can decide between an SPA and a multi-page app | Ask what the traffic looks like and how much state must survive navigation. An MPA gets browser navigation, caching and back for free; an SPA buys continuity at the price of reimplementing all three.MPA vs SPA → |
| Four teams want to deploy independently into one page | Micro frontends solve an organisational problem and add a runtime one: duplicated dependencies, versioned contracts, shared auth and a page that can now be broken by someone else's deploy.Micro Frontends → |
| Every team has its own button and none of them agree | A design system is a contract plus tokens plus a maintainer, not a folder of components. Without tokens the values fork again the first time a designer needs a variant.Design Tokens → |
| A timestamp shows the wrong day for some users | Store an absolute instant, format at the edge in the viewer's zone and locale. "Today" is a calendar question that depends on where the reader is standing.Timezones and Locale Formatting → |
| Translated strings overflow their buttons | Internationalization is a layout constraint as much as a string table: length varies, direction varies, and plural rules are not a ternary on n.Internationalization → |
| A flag is being used to hide a paid feature | A client-side flag is a rollout mechanism, never an authorization boundary. Evaluation lives in a bundle the user can read and edit.Feature Flags in the Client → |
| The frontend needs six calls to render one screen | The contract shape is driving UI complexity. Either the API changes or something aggregates for this client — and owning a backend for frontend is a real, ongoing cost.Backend for Frontend → |
| A large upload fails near the end and starts over | Upload UX is resumability, progress, cancellation and a client-side size and type check before a byte moves. The bytes should not travel through your own application server.File Upload UX → |
| A release looks fine in dashboards but users are complaining | Release health ties errors, vitals and crash-free sessions to a version so you can compare this deploy against the last one rather than against an absolute target.Release Health → |
| Session replay was turned on across the whole product | You are now recording user input, which is a privacy obligation before it is a debugging tool. Mask by default, allow-list what is captured, and be explicit about retention.Session Replay and the Privacy It Costs → |
| Bug reports say "it is broken" and nothing reproduces locally | Work the layers in order — network, console, DOM and computed style, application state, performance, memory — instead of guessing. A method finds the class of bug that a hunch never does.A Method for Frontend Bugs → |
| Model output is rendered as it streams and the layout jumps constantly | Streaming tokens are partial by definition: incomplete markdown, unterminated code fences, growing containers. Reserve space, parse defensively and make cancellation available at every point.Streaming a Response Without Melting the Device → |
| A model suggested an action and the UI performed it | A suggestion is not authorization. Any consequential action needs an explicit human confirmation and a server-side check that does not trust the client's claim about what the model said.A Suggestion Is Not an Authorization → |