Tailwind Wins: Making My Meta Stack Even More Meta

By Jay Griffin, GPT-5.3-Codex*GPT-5.3-Codex via GitHub Copilot·  February 23, 2026
🏷️ Tags:cssnextjsemotiontailwindshadcnarchitectureai

My migration plan from Emotion to Tailwind, CSS Modules, Shadcn, and CSS variables so styling stays fast, explicit, and AI-friendly.

Rethinking My Styling System

Thinking out loud about why I'm switching.

How I Got Here

I've been using Emotion for CSS-in-JS across my site for a while. The appeal was real: colocated styles, dynamic values, JS in your CSS. It felt powerful.

But honest self-assessment: 95% of what I actually use Emotion for is stuff that CSS modules handles natively. Scoped styles, colocated with the component, real CSS. The other 5% — dynamic computed values — turns out to be solvable with CSS custom properties passed as inline styles. No runtime library required.

Meanwhile Tailwind and shadcn have eaten the world for a reason. They're fast, AI codes them fluently, and shadcn components live in your codebase as real files you can read and modify. The AI-codability of a stack is now a legitimate architectural consideration.


The Key Realizations

Tailwind isn't powerful, it's fast. CSS is more powerful. But fast has real value, especially for iteration. The move is to use both intentionally rather than picking a lane.

CSS modules is just scoped CSS. Name your file .module.css, import it, use styles.className. The build tool handles the scoping. No runtime, no config, works out of the box in Next.js. The cascade was always the problem — CSS modules just fixes it.

CSS variables are the JS-in-CSS bridge. You don't need Emotion for dynamic styles. You pass computed values from JS as CSS custom properties via inline style, then consume them in your CSS file. JS owns the logic, CSS owns the appearance. Clean separation.

.tsx
<div
  className={styles.card}
  style={{ '--progress': progress, '--hue': hue } as React.CSSProperties}
>
.css
.card {
  width: calc(var(--progress) * 100%);
  background: hsl(var(--hue), 70%, 50%);
}

My theme editor was already broken. Styles throughout the app weren't using theme values consistently. The theme context was supposed to be the source of truth but got bypassed over time through iteration.

The primitive system is the real asset, not the styling layer. The component API I've built is worth preserving. Emotion underneath it is swappable.

The New Stack

PurposeTool
Component system + commodity UIshadcn + tailwind
Custom componentsCSS modules
Dynamic computed stylesCSS variables via inline style
ThemingCSS custom properties on :root
JS logicJS. Just JS.

No Emotion. No runtime styling cost. No theme context. The browser handles theming natively via CSS variables, and anything not using them is visibly, obviously hardcoded — no mystery.


The Plan

1. Set up the new foundation

2. Write the context document

A single markdown file that describes the stack for AI sessions:

This becomes the thing you paste at the top of every vibe session. The AI already knows shadcn cold — the context doc just tells it which components you have and how your specific system is wired.

3. Pick one page and do a full once-over

This is the proof of concept. If the new system clicks, you'll know immediately.

4. Migrate page by page

5. Fix the theme system for real

6. Build the AI vibecoding workflow


AI as Conductor

The quality of your building blocks determines the quality of AI's output.

Other UI libraries give you a black box npm package — opaque, unreadable, unmodifiable. The AI can't see inside it.

shadcn gives you the source. The component lives in /components/ui/button.tsx. The AI can read it, extend it, compose it, understand exactly what variants exist and why. Radix underneath handles all the hard stuff — accessibility, keyboard navigation, focus management, open/close state — invisibly and correctly.

So the AI's entire job becomes: pick the right building blocks, compose them, style them. All the hard problems are already solved. You've removed them from its plate.

Choosing your stack is now also choosing how well AI can help you. That's a new variable in the decision that didn't exist a few years ago.