Blog

CSS @scope: Style Encapsulation Without BEM or CSS Modules

The rule .card img was supposed to crop the product photo, and along the way it turned the author's round avatar into a squashed rectangle – because the avatar is also an image inside the card. BEM fixes this with a naming convention, CSS Modules with hashed class names, but neither can tell a selector where to stop. @scope can – and it brings a brand-new criterion into the cascade, proximity, which works differently than most examples on the web suggest.

How AI Sees Your Page: Content the Crawler Can't Find

A product page has a detailed description in one tab, a spec table in another and two hundred reviews below – and when someone asks an AI assistant about the product, the answer is based on two sentences from the meta description. Intuition says display: none is to blame. That's almost never true: crawlers read HTML, they don't look at the rendered page, so hiding something with styles doesn't bother them. What bothers them is content that isn't in the HTML at all – rendered conditionally, loaded on scroll, inserted by CSS or baked into an image. I show exactly where it gets lost and how to check it with one command.

AI Writes CSS Like It's 2015 – and How to Change That

You ask an AI assistant to fix a menu that hides under the hero section, and you get z-index: 9999 !important. You ask for a card that breaks in a narrow sidebar – you get another media query. The code works, but it looks like it came from a tutorial a decade ago, and every such fix makes the next one harder. It's not a weak-model problem: it's a consequence of what CSS the model learned from and what it can't see when it answers. I show where these habits come from, how to recognize them, and how a rules file and access to the real page change what the model writes.

React use() and Suspense: Streaming Data Without Waterfalls

Six cards on a dashboard, six spinners, and they don't appear together — they appear one after another, like dominoes, because each one only started fetching after the one above it finished. That's a waterfall, and the total wait isn't the slowest request, it's the sum of all of them. use() and Suspense break the chain, but the interesting part is the machinery underneath: what a suspended component actually throws, and how HTML that arrives out of order still lands in the right place.

Scroll-Driven Animations: animation-timeline Instead of IntersectionObserver

There's a reason JavaScript parallax always looks slightly wrong — a little detached, like the layers are swimming half a frame behind the page. It isn't sloppy code. It's that scroll events reach your JavaScript after the browser has already painted the scroll. The browser solved this once before, when it turned sticky headers into position: sticky. Scroll-driven animations are the same move, applied to every scroll-linked effect at once.

View Transitions in Next.js: Page Animations Without an Animation Library

For ten years, animating a thumbnail into a hero image meant one technique: measure the element before, measure it after, fake the difference with a transform, and let it play. FLIP. Every animation library does it, and every one of them pays for it with JavaScript on the main thread at the worst possible moment. The View Transitions API moves that whole trick into the browser — and since React 19.2 and Next.js 16, the entire contract is a single prop called name.