The blog

Writing from the workshop

202 articles on the parts that actually hurt.

section
Stop Hardcoding Color Variants. Relative Color Syntax Does It.
CSS

Stop Hardcoding Color Variants. Relative Color Syntax Does It.

A design system ships one brand color and four hand-picked hex variants for hover, pressed, border, and tint. Change the brand color and four of those five silently go stale. CSS relative color syntax derives every variant from the one color you actually update.

Nov 1, 2026 · 7 min
You're Writing Mouse and Touch Handlers. Pointer Events Does Both.
JavaScript

You're Writing Mouse and Touch Handlers. Pointer Events Does Both.

A draggable card that works with mousedown/mousemove on desktop and breaks the moment you test it on a phone. The fix isn't a second set of touch listeners — it's deleting the mouse ones and switching to Pointer Events, which was built to handle mouse, touch, and pen as one input.

Nov 1, 2026 · 6 min
NestJS 12: @Optional() Not Inherited by a Redeclared Constructor
Nestjs

NestJS 12: @Optional() Not Inherited by a Redeclared Constructor

NestJS 12 stops inheriting @Optional() once a subclass redeclares its own constructor — not when it has none at all. Cheat sheet included.

Oct 30, 2026 · 15 min
Stop Using a JS Masonry Library. CSS Grid Lanes Does It Natively.
CSS

Stop Using a JS Masonry Library. CSS Grid Lanes Does It Natively.

Masonry layouts have meant a JS library or a CSS column-count hack that scrambles reading order ever since Pinterest made the look popular. CSS Grid Lanes finally does it natively — here's the real support story and how to ship it today.

Oct 30, 2026 · 6 min
font-display: swap Fixes Invisible Text. It Also Shifts Your Layout.
CSS

font-display: swap Fixes Invisible Text. It Also Shifts Your Layout.

Setting font-display: swap kills the flash of invisible text, so most teams call the font-loading problem solved and move on. It doesn't — it just trades FOIT for a layout shift Lighthouse will happily dock you for. Here's the actual fix.

Oct 29, 2026 · 6 min
When saying no feels like volunteering to be next
The people around you

When saying no feels like volunteering to be next

Your team is smaller than it was a year ago and the backlog isn't. Saying no to one more thing now reads as a risk, not a boundary. Here's how to turn a no into a trade-off the other person can actually say yes to, and when that move doesn't work at all.

Oct 28, 2026 · 6 min
Stop Calling form.submit(). Use requestSubmit() Instead.
JavaScript

Stop Calling form.submit(). Use requestSubmit() Instead.

A Save button living outside the <form> tag, wired to form.submit(), looks like it works — until a blank required field sails through and the page does a full reload your fetch handler never saw coming. requestSubmit() is the method that actually clicks the button for you.

Oct 28, 2026 · 6 min
Next.js 16 updateTag vs revalidateTag: What Changed
Nextjs

Next.js 16 updateTag vs revalidateTag: What Changed

Next.js 16 deprecated revalidateTag's single-argument call and added updateTag. What the new profile argument changes, plus a copy-paste cheat sheet.

Oct 27, 2026 · 14 min
Stop Writing JS to Detect a Stuck Header. `scroll-state()` Fixes It.
CSS

Stop Writing JS to Detect a Stuck Header. scroll-state() Fixes It.

A sticky header that should only cast a shadow once it's actually stuck to the top usually means a hidden sentinel div and an IntersectionObserver. CSS now tracks that state itself — the `scroll-state()` container query reads it directly, no sentinel, no observer, no JS math.

Oct 27, 2026 · 5 min
Vue v-for: The Directive Unmounted Hook Bug, Fixed in 3.5.41
Vue

Vue v-for: The Directive Unmounted Hook Bug, Fixed in 3.5.41

A custom directive's unmounted hook could silently skip firing inside a keyed v-for list. The directive unmounted hook bug, Vue's real fix, and a cheat sheet.

Oct 26, 2026 · 13 min
You're Debouncing Scroll Wrong. requestAnimationFrame Fixes It.
JavaScript

You're Debouncing Scroll Wrong. requestAnimationFrame Fixes It.

Debouncing a scroll handler makes it wait until scrolling stops — which is why your sticky header always lags half a beat. requestAnimationFrame fixes the lag by syncing updates to the browser's own paint clock instead of a timer.

Oct 26, 2026 · 5 min
My Reducer's Switch Stopped Being Exhaustive. `as const` Fixed It.
TypeScript

My Reducer's Switch Stopped Being Exhaustive. as const Fixed It.

Object and array literals widen to `string` and `number` the moment you write them, which quietly breaks discriminated unions and exhaustive switch checks. `as const` keeps the literal types — but it also freezes the value, which bites in its own way.

Oct 25, 2026 · 7 min