About this article
As the fifth installment of the “Frontend Architecture” category in the series “Architecture Crash Course for the Generative-AI Era,” this article explains CSS design.
CSS is a language that’s easy to write but is guaranteed to collapse at scale. This article compares CSS authoring approaches (Tailwind/CSS Modules/CSS-in-JS), covers design systems and Design Tokens, accessibility, and the AI-era “Tailwind + shadcn/ui + Design Token” triad.
Other articles in this category
Easy to write, guaranteed to collapse at scale
The purpose of CSS design is to prevent that collapse, limit the impact range of style changes, and create a common language between designers and developers. The deciding quality factor is not just “does it work?” but “can we still safely modify it in the future?” - and conventions plus automation are needed to keep order.
The easy way to write CSS is also the way that collapses. Conventions and automation are everything.
Main CSS authoring approaches
Multiple ways to write CSS have appeared over the years. Each has its own philosophy, and you choose by project size and preference. Today Tailwind and CSS Modules are mainstream, with CSS-in-JS in decline.
flowchart LR
GLOBAL["Global CSS<br/>(legacy)"]
MOD["CSS Modules<br/>(auto-localized)"]
CIJ["CSS-in-JS<br/>(styled-components)"]
TW["Tailwind CSS<br/>(utility)"]
ZERO["Zero-runtime<br/>Vanilla Extract / Panda"]
GLOBAL -.|hits scope limits| MOD
MOD -.|need dynamic styles| CIJ
CIJ -.|runtime cost / no RSC| ZERO
CIJ -.|switch for productivity| TW
classDef legacy fill:#fee2e2,stroke:#dc2626;
classDef stable fill:#dbeafe,stroke:#2563eb;
classDef decline fill:#fef3c7,stroke:#d97706;
classDef modern fill:#dcfce7,stroke:#16a34a;
class GLOBAL legacy;
class MOD stable;
class CIJ decline;
class TW,ZERO modern;
| Approach | Examples |
|---|---|
| Global CSS | Plain CSS files (legacy) |
| CSS Modules | styles.module.css (class names auto-localized) |
| CSS-in-JS | styled-components / Emotion |
| Utility CSS | Tailwind CSS (class-list approach) |
| Zero-runtime CSS-in-JS | Vanilla Extract / Panda CSS |
CSS-in-JS once dominated, but issues with runtime overhead and RSC (React Server Components, Reacts components run on the server) incompatibility have been called out, and it’s gradually being replaced by zero-runtime approaches (Vanilla Extract / Panda) and Tailwind.
CSS Modules
CSS Modules is a mechanism that auto-localizes class names per file. Just naming a file styles.module.css causes class names to be hashed at build time, eliminating global collisions.
/* Button.module.css */
.primary { background: indigo; color: white; }
import styles from './Button.module.css'
<button className={styles.primary}>Submit</button>
Pros: Simple, low learning cost, zero runtime overhead, writing experience close to plain CSS. Cons: Changing styles dynamically via props is annoying, requires JS manipulation.
It’s natively supported by Next.js, Vite, and webpack, making it the go-to when you want to “prevent collisions while keeping a near-plain-CSS writing experience.”
CSS-in-JS
CSS-in-JS is the approach of writing CSS inside JavaScript. styled-components / Emotion are the representatives, letting components and styles coexist. The strength is the JS expressiveness - you can switch styles dynamically by props.
| Strengths | Weaknesses |
|---|---|
| Dynamic styles via props | Runtime overhead |
| Type-checkable | Poor RSC compatibility (doesn’t run on server) |
| Self-contained per component | Increases bundle size |
Because runtime processing is involved, it affects display speed; and since it doesn’t work in React Server Components, compatibility with the Next.js App Router era is poor and it’s gradually fading. In March 2024, styled-components officially announced entry into maintenance mode - a textbook case of an OSS that defined an era quietly closing up shop.
The trend today is toward zero-runtime CSS-in-JS (Vanilla Extract / Panda).
Tailwind CSS
Tailwind CSS is the approach of lining up utility classes in HTML, and is the largest modern current. You decide styles by listing class names like px-4 py-2 rounded bg-indigo-600 text-white.
<button class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700">
Submit
</button>
| Strengths | Weaknesses |
|---|---|
| Consistency stays naturally maintained | HTML gets long |
| No need to switch between files | Class name vocabulary requires learning |
| Build-time removal of unused CSS, lightweight | Hard to read at first sight |
Often called “ugly HTML,” but its biggest benefit is that the design system’s values (colors, spacing, font sizes) are enforced, making visual consistency easy to maintain on large teams. It’s the “top candidate” for CSS selection on new projects, and VS Code IntelliSense support is also rich.
First choice for new projects. Achieves design consistency and dev speed simultaneously.
Zero-runtime CSS-in-JS
What solves the “runtime overhead” of conventional CSS-in-JS is zero-runtime CSS-in-JS. Because “CSS is extracted at build time,” CSS-in-JS processing doesn’t run at runtime.
| Product | Notes |
|---|---|
| Vanilla Extract | Type-safe, theme support, adopted at Adobe |
| Panda CSS | Chakra UI lineage, Design Token integration |
| Linaria | styled-components compatible |
| StyleX | Meta-internal, adopted at Instagram etc. |
A big strength is being usable in RSC environments, so it’s chosen for scenes premised on server-side rendering like Next.js App Router. Vanilla Extract is “highly type-safe” - an option that lands well with TypeScript fans.
Design systems and Design Tokens
A design system is “a set of reusable UI parts and the rules for using them.” Google’s Material Design and Microsoft’s Fluent UI are well-known, bridging design and implementation.
| Element | Content |
|---|---|
| Design Token | Variabilizing colors, fonts, spacing, shadows, etc. |
| Component | Common UI like buttons, forms, modals |
| Pattern | Best practices for combinations |
| Documentation | Usage, principles, prohibitions |
A Design Token is a mechanism for “variabilizing design values like colors and sizes with meaningful names.” Calling it color.primary rather than #4F46E5 makes brand changes, dark-mode support, and theme switching all possible at once.
❌ color: #4F46E5 ← unclear which color is used where
✅ color: var(--color-primary) ← intent is clear
tokens.json:
{
"color.primary": "#4F46E5",
"color.primary.dark": "#818CF8",
"spacing.sm": "8px",
"spacing.md": "16px"
}
Tools like Style Dictionary that pull tokens defined in Figma into code have appeared, and “fully syncing tokens between Figma and code” has become the modern mainstream.
”The old color that kept bleeding through after rebranding” (industry stories)
There’s a story of a service handling a rebrand (changing brand color) by find-and-replacing #4F46E5 across all files. Most of the code changed, but inline styles, gradients copy-pasted directly from Figma, and email-template HTML kept the old color, and for a week after release the previous brand color kept bleeding through somewhere on the screen.
It’s a common testimonial: “I learned the hard way through similar experiences that ‘colors that aren’t variabilized cannot be fully grepped.’” If Design Tokens had been defined from the start, the work could have ended with rewriting one line of var(--color-primary). It’s spoken of as a textbook lesson that “hardcoded color codes always become technical debt later.”
Especially in environments where “designers paste colors via Figma copy-paste” or “newsletter HTML is handled by another team,” without tokens, “fully replacing without omissions is practically impossible.”
The moment you write #4F46E5, you’ve taken on a debt to your future self.
Choice of component library
Building UI components from scratch takes time, so using a ready-made component library is the modern standard. There are many options - choose to match your project’s color.
| Library | Characteristics |
|---|---|
| shadcn/ui | Copy-paste style, becomes your own code, hugely popular today |
| Radix UI | Accessibility-focused, no visuals |
| MUI (Material UI) | Largest, strong distinct look, feature-rich |
| Chakra UI | Design Token-centric, pleasant to write |
| Ant Design | Best for admin panels, abundant parts |
| HeadlessUI | Tailwind-premised lightweight option |
shadcn/ui is unusual - rather than an npm package, it uses the approach of “copying source code into your own project.” Because there’s no library dependency and you can freely modify, it’s supported by developers who dislike constraints.
Few constraints, want your own flavor: shadcn/ui + Radix. Want immediate results: MUI.
Accessibility (a11y)
Accessibility (a11y) is closely related to CSS design - it’s the metric for building “a UI usable by everyone, not just visually.” You consider screen-reader users, keyboard-only users, the visually impaired, and other diverse users.
| Principle | Examples |
|---|---|
| Sufficient contrast ratio | 4.5:1 or more between text and background |
| Visible focus | Reachable by keyboard Tab |
| Semantic HTML | Use <button>, no <div onclick> |
| ARIA (Accessible Rich Internet Applications) attributes | Auxiliary info for screen readers |
WCAG (Web Content Accessibility Guidelines, the international Web accessibility guideline) 2.2 AA compliance is the modern line for corporate sites, and “more countries are making it a legal obligation” for government sites. Lighthouse and axe-core enable automated checks, so “operations measured in CI” is recommended.
CSS variables and dark mode
CSS Variables (Custom Properties) are the foundational technology for dynamic theme switching. Just defining variables on :root and switching by class or attribute realizes dark-mode support.
:root {
--bg: white;
--fg: black;
}
[data-theme="dark"] {
--bg: #0b0f1e;
--fg: #e5e5e5;
}
body { background: var(--bg); color: var(--fg); }
Switching is just writing document.documentElement.dataset.theme = 'dark' in JS. Tailwind also natively supports the same mechanism via the dark: variant. Combine with the prefers-color-scheme media query that detects OS settings, and “automatic switching tied to the OS” is achievable.
Recommended composition by case
The CSS design stack varies by project nature. If you grasp the modern mainstream, you won’t go badly wrong.
- New SPA / mid-size SaaS - Tailwind + shadcn/ui + Design Token. The standard modern composition
- Blog / content site - plain CSS / CSS Modules (Astro’s scoped styles are sufficient)
- Building a large design system - Vanilla Extract / Panda CSS + in-house Design Tokens. Type-safety-focused
- Phased introduction into existing apps - CSS Modules (easy to introduce locally)
- In-house admin panel - MUI / Ant Design (rich parts, fast to build)
CSS quality numerical gates and accessibility standards
Note: Industry baseline values as of April 2026. Will become outdated as technology and the talent market shift, so requires periodic updates.
CSS design quality, today, is “tracked numerically” rather than being “kind of clean.” Accessibility (WCAG 2.2 AA) compliance is becoming a “legal obligation” in more countries.
| Item | Standard | Reason |
|---|---|---|
| Contrast ratio (body text) | 4.5:1 or more | WCAG 2.2 AA |
| Contrast ratio (large text) | 3:1 or more | WCAG 2.2 AA |
| Min size for touch targets | 44x44px | Apple HIG / WCAG |
| Focus ring visible | Required | Lifeline for keyboard users |
| Dark mode support | Recommended | prefers-color-scheme detection |
| Lighthouse Accessibility score | 90 or more | Minimum line for business sites |
| axe-core errors | 0 | Mechanically blocked in CI |
| CSS bundle size | < 50KB (after gzip) | Per-page upper-bound guideline |
Building “axe-core / Lighthouse” into CI is the modern standard. Accessibility lawsuits are increasing year over year in the US and EU, and after the 2019 Domino’s Pizza accessibility lawsuit (which went all the way to the US Supreme Court with the plaintiff prevailing), Web accessibility entered “the legal-risk territory.” Japan’s revised Act for Eliminating Discrimination against Persons with Disabilities (effective April 2024) points the same direction.
WCAG 2.2 AA is enforced mechanically in CI. Auto-check with axe-core + Lighthouse.
CSS design’s pitfalls and forbidden moves
Here are the typical accidents in CSS. They lead to scope collapse, specificity wars, and cascade hell.
| Forbidden move | Why it’s bad |
|---|---|
| Global CSS without naming conventions | Name collisions turn the codebase into “code too scary to touch” |
Heavy use of !important | Weaponizes specificity wars. The more you use it, the harder it is to unwind |
Inline styles (style="...") in production | Violates CSP, ignores Design Tokens, hard to fix |
Hardcoded color codes (#4F46E5 directly) | Areas that can’t be fully replaced remain at dark-mode/rebrand time |
| Newly adopting CSS-in-JS in RSC environments | styled-components entered maintenance mode in March 2024. Switch to Tailwind / Vanilla Extract |
Heavy use of Tailwind’s @apply | Kills Tailwind’s philosophy. Use class-list as is |
Ignoring semantic HTML (heavy <div onclick>) | Destroys accessibility, unreadable to screen readers |
<a href="#"> instead of <button> | Breaks under keyboard ops and ARIA roles |
| Verifying contrast by eyeballing only | Floods of colors below 4.5:1 emerge. Mechanical check with axe-core is mandatory |
| Implementing dark mode as an afterthought | Becomes a major job to variabilize all colors. Use Design Tokens from the start |
The 2019 Domino’s Pizza accessibility lawsuit involved a visually impaired user unable to order pizza via screen reader - it went to the Supreme Court with the plaintiff prevailing. Web accessibility has shifted from “consideration” to “legal risk.” In the US, the ADA applies; in the EU, the EAA (European Accessibility Act, effective June 2025) applies.
Hardcoded color codes are debt to your future self. Start with Design Tokens.
AI-era perspective
When AI-driven development is the premise, CSS design becomes “a Tailwind + shadcn/ui monopoly.” UI generation AIs (v0, Bolt, Lovable, etc.) produce Tailwind class lists with the highest accuracy, so UIs come together far faster than with other CSS approaches. shadcn/ui’s “copy-paste approach” has good chemistry with AI generation - the strength is that the AI can directly rewrite component implementations.
| Favored in the AI era | Disfavored in the AI era |
|---|---|
| Tailwind (AI generates flawlessly) | CSS-in-JS (no RSC, AI generation unstable) |
| shadcn/ui (copy-paste style, AI-editable) | Custom UI libraries |
| Design Tokens (variables make intent clear) | Hardcoded color codes |
| ARIA attributes, semantic HTML | AI-generated code full of <div onclick> |
In an era where UI-generation AI is exploding in capability, the workflow of “pasting Figma designs and having AI implement them” has reached a practical stage at the personal-to-mid-scale level. At that point, generation accuracy is in a state where Tailwind + React combination is a head above. With Design Tokens set up beforehand, you can prevent AI from generating code that ignores the brand color. AI tends to overlook accessibility (ARIA, contrast), so “wiring Lighthouse / axe-core auto-checks into CI” is the AI-era complement.
In the AI era, CSS overwhelmingly favors Tailwind + shadcn/ui + Design Tokens. Other approaches only when there’s a clear reason.
Common misconceptions
- You can muscle through with global CSS - at scale, name collisions and specificity wars turn the code into “code too scary to touch.” Splitting scope with tooling is the rule
- CSS-in-JS is modern - no RSC support, declining. styled-components is in maintenance mode. Avoid new adoption
- Avoid Tailwind because it’s ugly - true that HTML gets long, but the return on “design consistency and dev speed” is overwhelming. Becomes readable once you adapt
- Design Tokens are only for big companies - even in personal dev “hardcoded color codes always cause trouble later.” Bake variabilization in from day one
What to decide - what is your project’s answer?
For each of the following, try to articulate your project’s answer in 1-2 sentences. Starting work with these vague always invites later questions like “why did we decide this again?”
- CSS authoring approach (Tailwind / CSS Modules / CSS-in-JS)
- UI component library (shadcn/ui / MUI etc.)
- Design Token management (tokens.json / Style Dictionary)
- Naming convention (when adopting BEM etc.)
- Accessibility standard (WCAG AA etc.)
- Dark mode support (OS-linked / toggle)
- RTL (right-to-left scripts, Arabic etc.) support
- Icon system (Lucide / Heroicons etc.)
How to make the final call
The core of CSS design is “baking in mechanisms to prevent global collapse from the start.” CSS is a write-anything language, so without conventions at scale you always end up in name collisions, specificity wars, and cascade hell. The starting point is “how to separate scope,” and the choice is between solving with tooling (CSS Modules/Tailwind/zero-runtime CSS-in-JS) or solving by naming conventions (BEM etc.).
“Modern mainstream is tooling-side resolution” - humans hardly need to muscle through with naming conventions like BEM anymore. Another axis is Design Tokens - by variabilizing colors, spacing, and font sizes in code, brand changes, dark mode, and theme switching can be handled in one go. Hardcoded color codes always become debt later.
The decisive axis is the triad of “Tailwind + shadcn/ui + Design Tokens.” The Tailwind + React combination gets the highest-accuracy code from UI-generation AI (v0, Bolt, Lovable), and shadcn/ui’s copy-paste style meshes perfectly because AI can directly rewrite component implementations.
CSS-in-JS is in decline due to runtime overhead and lack of RSC support. CSS Modules survives in manual projects with its near-plain-CSS feel, but in AI-driven development Tailwind is overwhelmingly favored. “Accessibility (WCAG 2.2 AA) is an area AI tends to overlook,” so wiring Lighthouse/axe-core into CI to plug AI’s gaps is the complement.
The selection priorities, summarized:
- Tailwind + shadcn/ui as first choice (the AI era’s overwhelming standard)
- Always define Design Tokens (no hardcoding allowed)
- Avoid CSS-in-JS (no RSC support, AI accuracy drops)
- Auto-check accessibility (WCAG 2.2 AA + axe-core)
Summary
This article covered CSS design, including Tailwind/CSS Modules/CSS-in-JS, Design Tokens, and accessibility.
Lean toward Tailwind for CSS, and bake in Design Tokens from the start. Other approaches only when there’s a clear reason - that is the practical answer in 2026.
Next time we’ll cover BFF (Backend For Frontend).
Back to series TOC -> ‘Architecture Crash Course for the Generative-AI Era’: How to Read This Book
I hope you’ll read the next article as well.
📚 Series: Architecture Crash Course for the Generative-AI Era (35/89)