← All posts
·10 min read

Claude Code with Tailwind CSS: Design System Workflow

Claude CodeTailwind CSSWorkflowDesign System
Claude Code with Tailwind CSS: Design System Workflow

Why Tailwind CSS projects need explicit Claude Code configuration

Claude Code knows Tailwind CSS well. It knows every utility class, the responsive prefix system, the variant modifiers, and both the v3 JavaScript config and the v4 CSS-first @theme approach. On a fresh project with no configuration, it produces clean, functional Tailwind markup that runs correctly in the browser.

The problem appears at scale. Without project-specific rules, Claude Code makes arbitrary decisions on every session: it mixes text-sm and text-xs for body copy, uses rounded-md in some places and rounded-lg in others, reaches for hover:opacity-80 when your design system uses hover:brightness-95, and generates component variants with inline conditionals rather than CVA. The output is technically correct Tailwind. It is not your design system.

The fix is a CLAUDE.md that encodes your design tokens, component conventions, and variant patterns once. After that, Claude Code generates Tailwind markup that fits your project rather than a generic template. If you are new to Claude Code entirely, the Claude Code setup guide covers installation and authentication before any of this applies.

The Tailwind v4 shift and why it matters for Claude Code

Tailwind CSS v4 moved from JavaScript configuration to a CSS-first @theme approach. Instead of a tailwind.config.js file, your design tokens live directly in CSS:

/* src/styles/globals.css */
@import "tailwindcss";

@theme {
  /* Colors */
  --color-brand-50: oklch(97% 0.02 250);
  --color-brand-500: oklch(55% 0.18 250);
  --color-brand-900: oklch(25% 0.12 250);

  /* Typography */
  --font-family-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;

  /* Spacing */
  --spacing-px: 1px;
  --spacing-0: 0;
  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --spacing-8: 2rem;
  --spacing-16: 4rem;

  /* Border radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  --radius-full: 9999px;
}

This CSS-first approach has a direct benefit for Claude Code: Claude reads CSS natively. It understands CSS custom properties as semantic values and applies them consistently. The old JavaScript config required Claude to mentally parse the config object, convert it to utilities, and remember the mapping. The @theme block removes that translation step.

Your CLAUDE.md should reference this file explicitly. Claude Code reads the linked token file before generating any markup, which eliminates token drift.

The Tailwind CLAUDE.md

A good Tailwind CLAUDE.md answers four questions: where do tokens live, how are components structured, how are variants handled, and what patterns are off-limits?

# Tailwind CSS project rules

## Design tokens
- All tokens defined in src/styles/globals.css using @theme
- Read this file before generating any component markup
- Do not use arbitrary values (e.g. w-[347px]), use token-based utilities only
- Exception: one-off layout values in page-level files only

## Component conventions
- Components in src/components/{ComponentName}/index.tsx
- Tailwind classes in cva() from "class-variance-authority", not inline ternaries
- Class merging via cn() from src/lib/utils (wraps clsx + tailwind-merge)
- No @apply in component CSS, keep styles in the JSX className
- Responsive: mobile-first. Base = mobile, md: = tablet, lg: = desktop

## Variant patterns
- All component variants declared in the CVA config object, not in the JSX logic
- Compound variants for combinations (e.g. size=lg + intent=danger)
- Default variants declared in the defaultVariants field

## Dark mode
- Strategy: class-based (dark: modifier with .dark on <html>)
- Every color utility must have a dark: counterpart
- Do not use media-query dark mode (prefers-color-scheme), class only

## Hard rules
- No arbitrary values without a comment explaining why the token doesn't cover it
- No z-index utilities above z-50 without a comment
- No !important utilities, fix the specificity instead
- TypeScript strict mode is on. CVA variant types are inferred, not manually typed.

This file loads every session. The token reference prevents Claude from inventing values. The CVA rule eliminates the sprawling ternary conditionals that AI tools default to for component variants.

Configuring Claude Code to read your token file

Add a reference to your token file inside CLAUDE.md so Claude Code loads it automatically:

## Files to read at session start
- src/styles/globals.css, design tokens (@theme block)
- src/lib/utils.ts, cn() helper
- src/components/ui/button/index.tsx, reference component for variant patterns

This instruction runs before Claude touches any component. It loads the token map, the merge utility, and a reference component that demonstrates your expected output format. Claude Code then generates new components that match the pattern of the reference component rather than a blank-slate interpretation.

Building a design system component with CVA

The component authoring prompt structure that produces consistent output:

"Create a Badge component. Variants: intent (default, success, warning, danger), size (sm, md). It renders a <span> with appropriate padding, border radius, font size, and background color from the design tokens. Use CVA for variants, cn() for class merging. Include the TypeScript interface for the variant props."

With the CLAUDE.md and token file loaded, Claude Code generates:

// src/components/ui/badge/index.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const badgeVariants = cva(
  "inline-flex items-center font-medium ring-1 ring-inset",
  {
    variants: {
      intent: {
        default: "bg-brand-50 text-brand-900 ring-brand-200 dark:bg-brand-900/20 dark:text-brand-200 dark:ring-brand-800",
        success: "bg-green-50 text-green-800 ring-green-200 dark:bg-green-900/20 dark:text-green-300 dark:ring-green-800",
        warning: "bg-yellow-50 text-yellow-800 ring-yellow-200 dark:bg-yellow-900/20 dark:text-yellow-300 dark:ring-yellow-800",
        danger: "bg-red-50 text-red-800 ring-red-200 dark:bg-red-900/20 dark:text-red-300 dark:ring-red-800",
      },
      size: {
        sm: "px-2 py-0.5 text-xs rounded-sm",
        md: "px-2.5 py-1 text-sm rounded-md",
      },
    },
    defaultVariants: {
      intent: "default",
      size: "md",
    },
  }
);

interface BadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof badgeVariants> {}

export function Badge({ className, intent, size, ...props }: BadgeProps) {
  return (
    <span className={cn(badgeVariants({ intent, size }), className)} {...props} />
  );
}

Every color value traces back to your @theme tokens. Dark mode variants are present on every color utility. The TypeScript interface is inferred from the CVA config rather than manually duplicated.

Without the CLAUDE.md, the same prompt produces hardcoded hex values, missing dark mode, inline ternaries, and a manually typed props interface that drifts out of sync when you add variants.

The design system audit workflow

Once you have a component library, Claude Code can audit it for consistency. This is one of the highest-value workflows because design inconsistency accumulates invisibly:

"Audit all components in src/components/ui/. For each component, check: (1) are all color values from the @theme tokens, not arbitrary values, (2) does every color utility have a dark: counterpart, (3) are variants using CVA or inline ternaries, (4) is cn() used for all className merging. Report inconsistencies by component and file."

Claude reads every component file, cross-references the token list, and produces a prioritized list of inconsistencies. This surfaces the components that a new team member wrote without reading the conventions, the components that predate CVA, and the one-off fixes that introduced hardcoded values.

For large component libraries, run this audit after every significant addition. It keeps design debt from accumulating faster than you can address it.

Handling responsive design

Responsive Tailwind markup is where Claude Code shines when given a clear description, and struggles when given an ambiguous one. "Make it responsive" produces inconsistent breakpoint choices. A specific description produces exactly what you need.

The prompt structure that works:

"The ProductGrid component should display 1 column on mobile (< 768px), 2 columns on tablet (768px to 1024px), and 4 columns on desktop (> 1024px). Card gap should be 4 on mobile and 6 on desktop. Use the responsive prefix system, not media queries in CSS."

Claude Code generates:

<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-4">
  {products.map((product) => (
    <ProductCard key={product.id} product={product} />
  ))}
</div>

The mobile-first rule in CLAUDE.md means Claude always starts from the base (mobile) and adds md: and lg: prefixes for larger screens. Without that rule, it sometimes starts from desktop and uses max-md: overrides, which produces the same visual output but reverses the cascade.

Add the breakpoints your project uses to CLAUDE.md. If you only use md: and lg:, document that. Claude will not reach for sm: or xl: unless you explicitly need them.

Dark mode: the most common failure mode

Dark mode is where Claude Code produces the most inconsistent output without configuration. Without explicit rules, it generates components with incomplete dark mode (some utilities have dark: counterparts, others do not), mixed dark mode strategies (some components use class, others use media), and incorrect background/foreground pairings.

The CLAUDE.md rule that prevents this:

## Dark mode checklist (run for every component)
- bg- utilities: bg-{color} paired with dark:bg-{dark-color}
- text- utilities: text-{color} paired with dark:text-{dark-color}
- border- utilities: border-{color} paired with dark:border-{dark-color}
- ring- utilities: ring-{color} paired with dark:ring-{dark-color}
- Strategy is class-based only. Never prefers-color-scheme.

After adding this rule, ask Claude to audit an existing component for dark mode completeness:

"Audit src/components/ui/card/index.tsx for dark mode completeness. List every color utility that is missing a dark: counterpart. Fix them using the semantic color tokens from src/styles/globals.css."

This produces a file-by-file checklist and applies the fixes in one pass.

CLAUDE.md for a shared design system

If your design system is consumed by multiple projects, the CLAUDE.md lives in the design system repository and a project-level CLAUDE.md imports its rules. This is especially useful for agencies or teams maintaining a component library that is shared across client projects.

# Project CLAUDE.md

## Design system
- This project consumes the @yourorg/ui package
- Component conventions are defined in node_modules/@yourorg/ui/CLAUDE.md
- Read that file before adding any new UI components
- Do not duplicate components that already exist in the package
- Custom components extend the base design system, they do not replace tokens

Claude Code reads the imported file at the referenced path. The token definitions and variant conventions from the shared library apply to any component work in the consuming project. This prevents teams from reinventing patterns that are already solved in the shared library.

Multi-file editing for design token updates

When you update a design token, the change cascades across every component that uses it. Claude Code handles this well when you frame it as a multi-file task. For example, changing your primary brand color requires updating the @theme block and verifying no components use the old arbitrary value:

"Update the brand color scale in src/styles/globals.css: brand-500 changes from oklch(55% 0.18 250) to oklch(58% 0.20 255). After updating the token, search all component files for any arbitrary Tailwind values (e.g. text-[#hex]) that reference the old color and replace them with the token-based utility."

This is the same pattern described in the Claude Code multi-file editing guide. Claude reads the token file, updates it, then scans the entire component tree for references that need updating. The task completes as a single coherent change rather than a manual find-and-replace across dozens of files.

What Tailwind developers get wrong first

Three patterns appear consistently when Tailwind developers start using Claude Code.

Not loading the token file. Claude Code knows Tailwind's default token scale. Without a reference to your @theme block, it uses the default scale rather than your custom tokens. The output compiles and looks approximately right, but it uses blue-500 when your design system uses brand-500, rounded-lg when your system uses radius-md. One line in CLAUDE.md pointing to your globals.css file fixes this entirely.

Skipping CVA for variant components. When asked to build a button with primary, secondary, and ghost variants without specifying the approach, Claude generates inline ternaries: className={isPrimary ? 'bg-brand-500 text-white' : isGhost ? 'bg-transparent border...' : '...'}. This works but is not maintainable. The CVA rule in CLAUDE.md is the single highest-value instruction for Tailwind component work.

Asking for a full page in one prompt. "Build the pricing page" generates a page that looks plausible but ignores component boundaries, repeats markup instead of using components, and skips the token conventions. The better approach is component-by-component: define the data shape, build each section as a standalone component, then assemble. Each component is reviewable and correctable before the page-level composition.

Building a consistent Tailwind workflow

The configuration in this guide produces a setup where Claude Code generates Tailwind markup that uses your tokens, follows your variant conventions, handles dark mode completely, and fits your component boundaries.

The foundation is the two-file setup: a @theme CSS block for tokens and a CLAUDE.md that references it and encodes your component rules. Run one component authoring session end-to-end after setting this up, and adjust the rules based on what Claude generates. Most projects need two or three additional rules after the first session.

For the broader principles that apply across all Claude Code workflows, the Claude Code best practices guide covers session management and the plan-before-execute pattern that keeps complex UI tasks on track. For TypeScript-specific configuration that pairs with the Tailwind setup above, see the guide on using Claude Code with React, which covers the component authoring loop in detail.

Want a Tailwind design system setup that works from day one? Claudify includes a Tailwind CLAUDE.md template, CVA component patterns, and dark mode checklists. One command: npx create-claudify.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir