In the article on microinteractions I wrote that transform and opacity animate on the compositor thread, so even a heavy page shouldn't cause a button animation to stutter. That's still true — with one caveat that article left out: if the main thread is blocked by a long JS task, the animation may never get a chance to start, because the click handler itself is stuck in a queue. INP is the metric that measures exactly this gap — and scheduler.yield() is one of the few ways to actually close it.
A list of a thousand comments renders slowly, so you reach for react-window – and in exchange for smooth scrolling you lose Ctrl+F, page printing, and part of screen-reader navigation, because elements outside the window stop physically existing in the DOM. content-visibility solves the same problem differently: elements stay in the DOM, the browser simply skips the expensive layout and paint work for them until they're near the visible area – and, surprisingly, it can temporarily reveal them when you search for text inside.
In the accessibility article, a modal built with display:none that still lingered in the accessibility tree was an example of a mistake that comes from building a modal from scratch out of divs. The real question is: why did it have to be built from scratch at all? I look at what native <dialog> and the popover attribute give you for free – top layer, focus trapping, light dismiss – and where these two mechanisms differ just enough that mixing them up is a quick path to an inaccessible interface.
The 'Like' button responds instantly, the heart fills in, the counter goes up – before the server has even answered. This isn't the speed illusion from the microinteractions article, it's something further-reaching: the interface shows a state that doesn't exist yet and assumes it's about to. I look at how to build this safely – with a real way to back out of the lie when the assumption doesn't hold – and why a manual setState-plus-catch is a worse version of what useOptimistic gives you for free.
The utility class .mt-4 loses to the rule .card .card__header .card__title, even though it logically should win, since it was added later. That's not an accident — that's specificity doing exactly what it was designed to do. @layer introduces an entirely new axis for resolving conflicts, one that runs before specificity, not alongside it — and it has one pitfall so unintuitive that even some documentation on the web gets it wrong.
"use server" looks like syntactic sugar over a fetch call to an API route – and that's exactly why most implementations treat it like an ordinary function call. That's a mistake with real consequences: every function marked "use server" becomes a public endpoint on the network, and a form built on this mechanism works even before JavaScript has had a chance to load. I look at what actually happens behind that directive, and where the pitfalls sit that the docs don't make obvious at first glance.