Blog

Core Web Vitals: What INP Really Measures, and How scheduler.yield() Saves the Main Thread

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.

content-visibility: Offscreen Rendering Without List Virtualization

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.

Popover API and Native <dialog>: The End of Hand-Built Modals

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.

Optimistic UI in React: An Interface That Knowingly Lies

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.

CSS @layer: How Cascade Layers End the Specificity War

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.

Server Actions in Next.js: A Form That Works Before JavaScript Even Loads

"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.

CSS :has() — The Parent That Knows What's Happening Inside

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.

Container Queries: A Component That Knows Its Container, Not the Viewport

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.'

Building Accessible Components for Screen Readers

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.

Animated Microinteractions in Ecommerce

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.