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.
A form field should highlight red when the input inside it is invalid. Simple enough — except CSS spent 25 years unable to ask an element about what's happening inside it, only the reverse. I look at how :has() flips that direction, how it differs from ordinary combinators, and where this new power genuinely costs you performance.
The same product card looks great in a grid and falls apart in a narrow sidebar — despite carefully tuned media queries. The problem isn't in your code, it's in the question a media query asks: about screen width, not about the space the component actually got. I look at how container queries move that question to where it should have been asked all along, and what actually happens under the hood when an element becomes a 'query container.'
Correct HTML is only the starting point. See how the accessibility tree actually works, why ARIA does more harm than good more often than you'd think, and build a real accordion, a live region, and Next.js focus handling with me.
Two stores, the same product, the same server response time – yet one feels faster and more trustworthy. The difference comes down to microinteractions: a few hundred milliseconds of animation that decide whether a user trusts what's on screen. I break them down from first principles – the trigger/feedback model, what actually happens inside the browser's rendering engine, and prefers-reduced-motion.
CSS Houdini isn't a single technology — it's a collection of specifications at very different stages of maturity. Find out which part of it is actually production-ready today, how @property and the paint worklet really work, and why the rest is still experimental.