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 saysThink
The click registers but the UI does not change for an uncomfortable momentInteraction 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 appearedVisual 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 finishedFollow 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 itSplit 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 endpointDeduplication 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 replacedA 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 tripOptimistic 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 whileHydration. 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 overLayout 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 unusableVirtualization: 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 respondingMove 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 atThe 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 signalA 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 CMSThis 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 policyThe 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 changedWeb 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 keyboardThe 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 anythingDiv 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 slowEach 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 valuesKeys 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 notResolution 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 variantCustom 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 profileSelector 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 trivialAsk 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 worseEvery 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 workingA 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 UIMicrotasks 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 visibleBreak 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 devicesA 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 inlineStructured 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 listenerDelegate 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-clickDefault 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 itFocus 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 nothingColour 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 doesNative 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 itA 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 actionHover 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 sidebarIt 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 phoneGive 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 apartOnly 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 itProp 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 localServer 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 positionClient-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 spinnerThe 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 failsLoading 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 dataThe 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 refetchingStale-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 restartsEvery 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 connectionLive 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 twiceAt-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 formExpiry 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 protectedHiding 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 anotherTabs 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 tokenThe 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 itCSRF 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 managerSame-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 scriptA 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 invisibleClickjacking 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 defaultIt 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 paintWeb 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 assetsA 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 returnsYou 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 optimisingGet 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 togetherA 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 paintRender-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 everythingResource 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 deployLong-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 downloadsThe 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 nothingElimination 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 libraryRead 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 browserDown-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 fileSource 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 fasterIt 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 openRetention: 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 rendersA 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 rendersStreaming 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 widgetIslands: 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 appAsk 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 pageMicro 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 agreeA 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 usersStore 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 buttonsInternationalization 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 featureA 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 screenThe 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 overUpload 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 complainingRelease 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 productYou 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 locallyWork 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 constantlyStreaming 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 itA 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 →