DESIGN.md vs tailwind.config.js — Why AI Agents Need the Semantic Wrapper

Comparison between DESIGN.md and putting tokens directly in tailwind.config.js. Why LLMs struggle to parse JavaScript configs and how DESIGN.md fixes it.

DESIGN.mdtailwind.config.js

Quick Verdict

If you use Tailwind and AI coding agents (which is basically everyone in 2026), use both. The tailwind.config.js is the runtime, where the framework reads your tokens. DESIGN.md is the semantic manual, where the agent understands why those tokens exist and how to apply them. Putting everything in tailwind.config and expecting Cursor to understand intent is like handing someone a dictionary and expecting them to write poetry.


Comparison Table

DimensionDESIGN.mdtailwind.config.js
FormatYAML tokens + Markdown rationaleJavaScript/TypeScript object
Primary purposeSemantic documentation for humans and AITailwind runtime configuration
LLM consumptionExcellent. Native format for LLMsProblematic. Nested JS, spread operators, dynamic imports
Design decisionsEmbedded in the documentNonexistent. Just raw values
PortabilityFramework-agnosticTailwind only (and derivatives like UnoCSS with adapter)
ExecutableNo. It is documentationYes. Tailwind reads it and generates CSS
ConflictsRare. Small focused fileCommon. Merge conflicts in large JS objects
MaintenanceUpdates when design system changesUpdates when design system changes (duplication)

The Real Problem: LLMs and JavaScript Config

Here is the elephant in the room that nobody discussed until AI agents went mainstream: LLMs are terrible at parsing complex JavaScript configuration.

A typical tailwind.config.js from a real project looks like this:

import { fontFamily } from 'tailwindcss/defaultTheme'
import colors from './tokens/colors.mjs'
import typography from './tokens/typography.mjs'

/** @type {import('tailwindcss').Config} */
export default {
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
  theme: {
    extend: {
      colors: {
        ...colors,
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        surface: {
          DEFAULT: 'hsl(var(--surface))',
          elevated: 'hsl(var(--surface-elevated))',
        }
      },
      fontFamily: {
        display: ['Cal Sans', ...fontFamily.sans],
        body: ['Inter', ...fontFamily.sans],
      },
      spacing: generateSpacingScale(4),
      borderRadius: {
        ...defaultRadius,
        card: 'var(--radius-card)',
      }
    }
  },
  plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
}

What the LLM actually sees here:

  1. Dynamic imports. It cannot know what colors.mjs contains without reading another file.
  2. Spread operators. ...colors is a black box.
  3. CSS variables. hsl(var(--primary)) is not a value, it is a reference.
  4. Functions. generateSpacingScale(4) requires mental execution.
  5. Zero context. Why Cal Sans? Why a 4px ratio? Total silence.

When the agent gets a prompt like “create a pricing card following our design system,” it reads this config and… guesses. It uses colors that look right. Applies spacing that seems close enough. The result compiles, but it does not respect the intent.


DESIGN.md in Detail

The semantic wrapper

DESIGN.md does not replace tailwind.config. It wraps the same tokens with meaning:

colors:
  primary:
    value: "hsl(222 47% 11%)"
    tailwind: "primary"
    usage: "CTAs, primary links, section headers"
    contrast: "Always pair with text-white or primary-foreground"
  surface:
    default:
      value: "hsl(0 0% 100%)"
      tailwind: "surface"
      usage: "Default background for cards and containers"
    elevated:
      value: "hsl(210 20% 98%)"
      tailwind: "surface-elevated"
      usage: "Cards with elevation, modals, popovers"

typography:
  philosophy: |
    Escala modular com ratio 1.25 (Major Third).
    Display usa Cal Sans para personalidade.
    Body usa Inter para legibilidade em telas.
    Nunca misturar display em corpo de texto.
  scale:
    - name: "hero"
      class: "text-5xl font-display font-bold"
      usage: "Apenas hero sections — um por página"
    - name: "section-title"
      class: "text-3xl font-display font-semibold"
      usage: "Títulos de seção — h2 na hierarquia"
    - name: "body"
      class: "text-base font-body"
      usage: "Texto corrido — parágrafos, descrições"

spacing:
  base: "4px"
  philosophy: |
    Múltiplos de 4px. Componentes internos usam 2-4 (8-16px).
    Entre componentes usa 6-8 (24-32px).
    Entre seções usa 16-24 (64-96px).
    Nunca usar valores arbitrários como p-[13px].

What changes in practice

With this DESIGN.md in context, the same “create a pricing card” prompt produces:

  • Correct colors with proper contrast
  • Hierarchical typography respecting the scale
  • Consistent spacing following the system
  • Correct Tailwind classes (it will not invent bg-slate-50 when it should be bg-surface-elevated)

Specific advantages over config alone

  1. Self-contained. Everything in one file, no imports to resolve.
  2. Explicit intent. “Never use display in body text” prevents errors before they happen.
  3. Mapped classes. The agent knows exactly which Tailwind class to apply.
  4. Usage examples. Concrete scenarios, not just abstract values.
  5. Documented constraints. What NOT to do matters as much as what to do.

tailwind.config.js in Detail

What it does well

The tailwind.config.js is execution. It is what the framework actually reads to generate CSS. Without it, there is no customized Tailwind. Full stop.

When it is enough on its own

In projects where:

  • You do not use AI agents to generate code
  • Small team (1-2 devs) who know the config by heart
  • Simple design system (few colors, standard typography)
  • Nobody new will join the project anytime soon

Real problems without DESIGN.md

Scenario 1: New developer joins the team

“What is the difference between bg-surface and bg-surface-elevated? When do I use each?”

Answer from tailwind.config: silence. Answer from DESIGN.md: “Surface for standard cards, surface-elevated for cards with visual elevation, like modals, popovers, and tooltips.”

Scenario 2: AI agent generating a page

Prompt: “Create a landing page with a hero, features grid, and pricing cards.”

Without DESIGN.md: The agent uses text-4xl because it seems right, mixes font-bold and font-semibold randomly, applies gap-4 and gap-6 without criteria.

With DESIGN.md: Hero uses text-5xl font-display font-bold (one per page). Features uses text-3xl font-display font-semibold. Section spacing is py-24. Automatic consistency.

Scenario 3: Design system evolves

You changed primary from blue to purple. In tailwind.config, the value changes and that is it. But what about contrast rules? What about pairings that used to work? What about combinations that now break?

DESIGN.md documents those relationships. The agent knows that changing primary affects CTAs, links, and headers, and it can adjust everything in a coordinated way.


The Correct Relationship: Layers

┌─────────────────────────────────────┐
│         DESIGN.md                    │  ← Semantics, intent, constraints
│  (consumed by AI + humans)           │
├─────────────────────────────────────┤
│      tailwind.config.js              │  ← Runtime, executable values
│  (consumed by Tailwind CLI)          │
├─────────────────────────────────────┤
│      Generated CSS                   │  ← Final output
│  (consumed by the browser)           │
└─────────────────────────────────────┘

DESIGN.md does not compete with tailwind.config. It sits above, as the layer that translates tokens into intent. The tailwind.config continues to exist, continues to be what Tailwind reads. But the agent reads DESIGN.md first and then knows which Tailwind classes to use.


When to Use Which

Use tailwind.config alone when:

  • Small personal project, no AI agents involved
  • You are the only developer and know every token by memory
  • Minimal design system (brand colors + default typography)
  • Nobody will ever need to be onboarded to this project

Use DESIGN.md + tailwind.config when:

  • Any project using AI coding agents. That covers 90% of projects in 2026.
  • Team with more than 1 developer
  • Design system with more than 10 custom tokens
  • Visual consistency across pages and components matters
  • Open-source where contributors need to understand the design

Use DESIGN.md without Tailwind when:

  • Project uses another CSS approach (vanilla, CSS Modules, styled-components)
  • Design tokens need to be framework-agnostic
  • Migrating from Tailwind to another solution

FAQ

Does DESIGN.md need to stay manually synced with tailwind.config?

Most of the time, yes, but it is less painful than it sounds. DESIGN.md changes rarely because a design system is stable by definition. When it does change, you update both. Community scripts exist that generate tailwind.config from DESIGN.md, inverting the direction: DESIGN.md becomes the canonical source and the config is derived.

If my tailwind.config is already well-organized, do I really need DESIGN.md?

Here is the test: does your AI agent generate consistent code? If you ask “create a card” and the result uses wrong tokens or mixes styles inconsistently, the config alone is not working as documentation. DESIGN.md fills exactly that gap.

Can I generate DESIGN.md from my existing tailwind.config?

Yes. The designmd.app offers automatic generation of DESIGN.md from existing configs. But the real value comes from adding the rationale, the explanations of why, which no script can extract automatically from the config.

What about Tailwind v4 with CSS-first config?

Tailwind v4 moves tokens into @theme in CSS, which is slightly more readable than the old JS config. But the fundamental problem remains: CSS does not carry usage semantics, constraints, or design philosophy. DESIGN.md remains the necessary semantic layer.

How many tokens justify creating a DESIGN.md?

If your tailwind.config has more than 5 custom colors, non-default typography, or custom spacing, you are already there. In practice, any project that has customized Tailwind beyond defaults benefits from DESIGN.md. The cost to create it is low (30 minutes), and the consistency benefit is permanent.


Conclusion

tailwind.config.js is infrastructure. DESIGN.md is communication.

One tells Tailwind “generate these CSS classes with these values.” The other tells the agent (and the human) “use these classes in these contexts for these reasons.”

The confusion happens when teams treat the config as documentation. It is not. It never was. It is a JavaScript object that configures a build tool. Trying to extract design intent from extend.colors.primary.DEFAULT: 'hsl(var(--primary))' is like trying to understand a recipe by reading only the ingredient list without the instructions.

DESIGN.md is the instructions. And in 2026, with AI agents cooking most of the frontend, instructions matter more than ever.

The 461 design systems on designmd.app include most of the relevant Tailwind projects, and each one demonstrates how the semantic wrapper transforms configuration into comprehension.