Skip to main content

Overview

The Tailwind v4 + Shadcn UI skill provides a production-tested setup pattern for Tailwind CSS v4 with shadcn/ui components. This skill implements the new CSS-first architecture using @theme inline patterns and prevents 8 documented errors that commonly occur during migration and setup.
Production-Tested: This skill is battle-tested in production at WordPress Auditor

What This Skill Does

The skill guides AI agents through the complete Tailwind v4 + shadcn/ui setup:
  • Four-Step Architecture - CSS variables → Tailwind mapping → base styles → automatic dark mode
  • Prevents 8 Errors - Documented solutions for common setup failures
  • Dark Mode Setup - Complete ThemeProvider and toggle implementation
  • Migration Guide - v3 → v4 migration with community-tested patterns
  • OKLCH Color Space - Modern color system with automatic fallbacks
  • Built-in Features - Container queries, line-clamp (no plugins needed)

Location in TailStack Packages

The Tailwind v4 + Shadcn skill is available in the React package:
# Agent skill files
packages/react/.agent/skills/tailwind-v4-shadcn/
packages/react/.claude/skills/tailwind-v4-shadcn/
packages/react/.cursor/skills/tailwind-v4-shadcn/
packages/react/.opencode/skills/tailwind-v4-shadcn/

Skill Structure

Skill Contents

Each skill directory contains:
  • SKILL.md - Complete setup guide (666 lines)
  • templates/ - Production-ready file templates
    • index.css - Complete CSS with color variables
    • components.json - shadcn/ui v4 configuration
    • vite.config.ts - Vite + Tailwind plugin setup
    • theme-provider.tsx - Dark mode provider
    • utils.ts - cn() utility function
  • references/ - Deep dive documentation
    • architecture.md - Four-step pattern explanation
    • dark-mode.md - Complete dark mode guide
    • common-gotchas.md - Troubleshooting
    • migration-guide.md - v3 → v4 migration
  • commands/ - Setup automation
    • setup.md - Installation commands

The Four-Step Architecture

Critical: You must follow all four steps in order. Skipping any step will break your theme.
1

Step 1: Define CSS Variables at Root

Define color variables at the root level (NOT inside @layer base).
/* src/index.css */
@import "tailwindcss";
@import "tw-animate-css";

:root {
  --background: hsl(0 0% 100%);      /* hsl() wrapper required */
  --foreground: hsl(222.2 84% 4.9%);
  --primary: hsl(221.2 83.2% 53.3%);
}

.dark {
  --background: hsl(222.2 84% 4.9%);
  --foreground: hsl(210 40% 98%);
  --primary: hsl(217.2 91.2% 59.8%);
}
2

Step 2: Map Variables to Tailwind Utilities

Use @theme inline to generate utility classes.
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  /* map ALL CSS variables */
}
Without this step, utilities like bg-background won’t exist.
3

Step 3: Apply Base Styles

Reference variables directly in base styles.
@layer base {
  body {
    background-color: var(--background);  /* NO hsl() wrapper */
    color: var(--foreground);
  }
}
Never double-wrap: hsl(var(--background)) is incorrect
4

Step 4: Result - Automatic Dark Mode

Your components now automatically switch themes.
<div className="bg-background text-foreground">
  {/* No dark: variants needed */}
</div>

Common Errors Prevented

This skill prevents 8 documented errors:
Error: “Cannot find module ‘tailwindcss-animate’”Solution:
# ✅ Correct for v4
pnpm add -D tw-animate-css

# ❌ Wrong (v3 only)
npm install tailwindcss-animate
Add to src/index.css:
@import "tailwindcss";
@import "tw-animate-css";
Error: bg-primary doesn’t apply stylesCause: Missing @theme inline mappingSolution: Add the @theme inline block (Step 2 of the architecture)
Error: Theme stays light or darkCause: Missing ThemeProviderSolution: Use the template from templates/theme-provider.tsx and wrap your app
Error: “Duplicate @layer base” in consoleCause: shadcn init adds @layer base - don’t add anotherSolution: Use single @layer base block for all base styles
Error: “Unexpected config file”Cause: v4 doesn’t use tailwind.config.ts (v3 legacy)Solution:
rm tailwind.config.ts
All configuration happens in CSS using @theme directive.
Issue: Dark mode doesn’t switch when using custom variantsCause: @theme inline bakes VALUES at build timeSolution: Use @theme (without inline) for multi-theme setups:
@theme {
  --color-text-primary: var(--color-slate-900);
}

@layer theme {
  [data-mode="dark"] {
    --color-text-primary: var(--color-white);
  }
}
Error: Cannot apply unknown utility classCause: v4 doesn’t support @apply with @layer base/componentsMigration:
/* ❌ v3 pattern */
@layer components {
  .custom-button {
    @apply px-4 py-2 bg-blue-500;
  }
}

/* ✅ v4 pattern */
@utility custom-button {
  @apply px-4 py-2 bg-blue-500;
}
Issue: Base styles seem ignoredCause: v4 uses native CSS layers - if not ordered, utilities override baseSolution: Define layer order explicitly or don’t use @layer base
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/base.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);

Quick Setup Guide

1

Install Dependencies

pnpm add tailwindcss @tailwindcss/vite
pnpm add -D @types/node tw-animate-css
pnpm dlx shadcn@latest init
2

Configure Vite

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: { alias: { '@': path.resolve(__dirname, './src') } }
})
3

Update components.json

{
  "tailwind": {
    "config": "",              // ← Empty for v4
    "css": "src/index.css",
    "baseColor": "slate",
    "cssVariables": true
  }
}
4

Delete Legacy Config

rm tailwind.config.ts  # v4 doesn't use this
5

Follow Four-Step Architecture

Implement all four steps as documented above in your src/index.css

Dark Mode Implementation

// src/components/theme-provider.tsx
import { createContext, useContext, useEffect, useState } from 'react'

type Theme = 'dark' | 'light' | 'system'

type ThemeProviderProps = {
  children: React.ReactNode
  defaultTheme?: Theme
  storageKey?: string
}

const ThemeProviderContext = createContext<ThemeProviderState | undefined>(undefined)

export function ThemeProvider({
  children,
  defaultTheme = 'system',
  storageKey = 'vite-ui-theme',
  ...props
}: ThemeProviderProps) {
  const [theme, setTheme] = useState<Theme>(
    () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
  )

  useEffect(() => {
    const root = window.document.documentElement
    root.classList.remove('light', 'dark')

    if (theme === 'system') {
      const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
        ? 'dark'
        : 'light'
      root.classList.add(systemTheme)
      return
    }

    root.classList.add(theme)
  }, [theme])

  const value = {
    theme,
    setTheme: (theme: Theme) => {
      localStorage.setItem(storageKey, theme)
      setTheme(theme)
    },
  }

  return (
    <ThemeProviderContext.Provider {...props} value={value}>
      {children}
    </ThemeProviderContext.Provider>
  )
}

What’s New in Tailwind v4

OKLCH Color Space

Perceptually uniform colors
  • Better gradients (no muddy middle)
  • Wider color gamut
  • Automatic sRGB fallbacks
  • 93.1% browser support
@theme {
  --color-brand: oklch(0.7 0.15 250);
}

Built-in Features

No plugins needed
  • Container queries built-in
  • Line clamp built-in
  • Typography plugin available
  • Forms plugin available
<div className="@container">
  <div className="@md:text-lg">Responsive</div>
</div>

Container Queries (Built-in)

<div className="@container">
  <div className="@md:text-lg @lg:grid-cols-2">
    Content responds to container width, not viewport
  </div>
</div>

Line Clamp (Built-in)

<p className="line-clamp-3">Truncate to 3 lines with ellipsis...</p>
<p className="line-clamp-[8]">Arbitrary values supported</p>
<p className="line-clamp-(--teaser-lines)">CSS variable support</p>

Migration from v3

Key Changes

  1. Delete tailwind.config.ts
  2. Move theme to CSS with @theme inline
  3. Replace tailwindcss-animate with tw-animate-css
  4. Update plugins: require()@plugin
  5. Use Vite plugin instead of PostCSS (recommended)
Automated migration tool often fails - Manual migration recommended

Default Element Styles Removed

Tailwind v4 takes a minimal approach - headings, lists, buttons lose default styles. Solution 1: Typography plugin
pnpm add -D @tailwindcss/typography
@import "tailwindcss";
@plugin "@tailwindcss/typography";
<article className="prose dark:prose-invert">
  {/* All elements styled automatically */}
</article>
Solution 2: Custom base styles
@layer base {
  h1 { @apply text-4xl font-bold mb-4; }
  h2 { @apply text-3xl font-bold mb-3; }
  ul { @apply list-disc pl-6 mb-4; }
}

Using with AI Agents

Claude Desktop & Claude.ai

1

Access Templates

Navigate to packages/react/.claude/skills/tailwind-v4-shadcn/templates/
2

Request Setup

Example: “Set up Tailwind v4 with shadcn/ui following the skill templates”
3

Reference Rules

Example: “Fix this Tailwind setup using the common-gotchas guide”

Cursor

1

Add to Rules

Include in .cursorrules:
When setting up Tailwind v4, follow .cursor/skills/tailwind-v4-shadcn/SKILL.md
2

Use Templates

Request specific templates: “Use the theme-provider template from tailwind-v4-shadcn”

OpenCode

1

Skill Available

Automatically loaded from packages/react/.opencode/skills/tailwind-v4-shadcn/
2

Request Setup

Example: “Initialize Tailwind v4 with dark mode using the skill templates”

Setup Checklist

Pre-Flight Checklist

  • @tailwindcss/vite installed (NOT postcss)
  • vite.config.ts uses tailwindcss() plugin
  • components.json has "config": ""
  • NO tailwind.config.ts exists
  • src/index.css follows 4-step pattern:
    • :root/.dark at root level (not in @layer)
    • Colors wrapped with hsl()
    • @theme inline maps all variables
    • @layer base uses unwrapped variables
  • ThemeProvider wraps app
  • Theme toggle works

Critical Rules

✅ Always Do

  1. Wrap colors with hsl() in :root/.dark
  2. Use @theme inline to map CSS variables
  3. Set "tailwind.config": "" in components.json
  4. Delete tailwind.config.ts
  5. Use @tailwindcss/vite plugin

❌ Never Do

  1. Put :root/.dark inside @layer base
  2. Use .dark { @theme { } } pattern
  3. Double-wrap: hsl(var(--background))
  4. Use tailwind.config.ts for theme
  5. Use @apply directive (deprecated)

Official Documentation

Learn More

Version Information

  • Skill Version: 3.0.0
  • Tailwind CSS: 4.1.18 (Latest)
  • Production: WordPress Auditor
  • Last Updated: January 20, 2026
  • Browser Support: Chrome 111+, Firefox 113+, Safari 15.4+

Build docs developers (and LLMs) love