Frontend Architecture

[Frontend Architecture] CSS Design - Tailwind/CSS Modules/Design Token

[Frontend Architecture] CSS Design - Tailwind/CSS Modules/Design Token

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.

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;
ApproachExamples
Global CSSPlain CSS files (legacy)
CSS Modulesstyles.module.css (class names auto-localized)
CSS-in-JSstyled-components / Emotion
Utility CSSTailwind CSS (class-list approach)
Zero-runtime CSS-in-JSVanilla 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.

StrengthsWeaknesses
Dynamic styles via propsRuntime overhead
Type-checkablePoor RSC compatibility (doesn’t run on server)
Self-contained per componentIncreases 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>
StrengthsWeaknesses
Consistency stays naturally maintainedHTML gets long
No need to switch between filesClass name vocabulary requires learning
Build-time removal of unused CSS, lightweightHard 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.

ProductNotes
Vanilla ExtractType-safe, theme support, adopted at Adobe
Panda CSSChakra UI lineage, Design Token integration
Linariastyled-components compatible
StyleXMeta-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.

ElementContent
Design TokenVariabilizing colors, fonts, spacing, shadows, etc.
ComponentCommon UI like buttons, forms, modals
PatternBest practices for combinations
DocumentationUsage, 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.

LibraryCharacteristics
shadcn/uiCopy-paste style, becomes your own code, hugely popular today
Radix UIAccessibility-focused, no visuals
MUI (Material UI)Largest, strong distinct look, feature-rich
Chakra UIDesign Token-centric, pleasant to write
Ant DesignBest for admin panels, abundant parts
HeadlessUITailwind-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.

PrincipleExamples
Sufficient contrast ratio4.5:1 or more between text and background
Visible focusReachable by keyboard Tab
Semantic HTMLUse <button>, no <div onclick>
ARIA (Accessible Rich Internet Applications) attributesAuxiliary 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.

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.

ItemStandardReason
Contrast ratio (body text)4.5:1 or moreWCAG 2.2 AA
Contrast ratio (large text)3:1 or moreWCAG 2.2 AA
Min size for touch targets44x44pxApple HIG / WCAG
Focus ring visibleRequiredLifeline for keyboard users
Dark mode supportRecommendedprefers-color-scheme detection
Lighthouse Accessibility score90 or moreMinimum line for business sites
axe-core errors0Mechanically 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 moveWhy it’s bad
Global CSS without naming conventionsName collisions turn the codebase into “code too scary to touch”
Heavy use of !importantWeaponizes specificity wars. The more you use it, the harder it is to unwind
Inline styles (style="...") in productionViolates 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 environmentsstyled-components entered maintenance mode in March 2024. Switch to Tailwind / Vanilla Extract
Heavy use of Tailwind’s @applyKills 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 onlyFloods of colors below 4.5:1 emerge. Mechanical check with axe-core is mandatory
Implementing dark mode as an afterthoughtBecomes 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 eraDisfavored 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 HTMLAI-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:

  1. Tailwind + shadcn/ui as first choice (the AI era’s overwhelming standard)
  2. Always define Design Tokens (no hardcoding allowed)
  3. Avoid CSS-in-JS (no RSC support, AI accuracy drops)
  4. 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.