Ai tailwind-v4-+-shadcn/ui-stack

Tailwind v4 + shadcn/ui Stack

install
source · Clone the upstream repo
git clone https://github.com/wpank/ai
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/wpank/ai "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/frontend/tailwind-v4-shadcn" ~/.claude/skills/wpank-ai-tailwind-v4-shadcn-ui-stack && rm -rf "$T"
manifest: skills/frontend/tailwind-v4-shadcn/SKILL.md
source content

Tailwind v4 + shadcn/ui Stack

Production-tested setup for Tailwind v4 with shadcn/ui. Prevents 8 documented errors through a mandatory four-step architecture.

WHAT

Complete Tailwind v4 + shadcn/ui configuration:

  • Four-step theming architecture (mandatory)
  • CSS variable-based color system
  • Automatic dark mode switching
  • Error prevention for 8 common mistakes
  • Migration guide from v3
  • Production-ready templates

WHEN

  • Starting a new React/Vite project with Tailwind v4
  • Migrating from Tailwind v3 to v4
  • Setting up shadcn/ui with Tailwind v4
  • Debugging: colors not working, dark mode broken, build failures
  • Fixing
    @theme inline
    ,
    @apply
    , or
    @layer base
    issues

KEYWORDS

tailwind v4, tailwindcss 4, shadcn, shadcn/ui, @theme inline, dark mode, css variables, vite, tw-animate-css, tailwind config, migration

Production verified: WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev)
Versions: tailwindcss@4.1.18, @tailwindcss/vite@4.1.18

Installation

OpenClaw / Moltbot / Clawbot

npx clawhub@latest install tailwind-v4-shadcn

Quick Start

# 1. Install dependencies
pnpm add tailwindcss @tailwindcss/vite
pnpm add -D @types/node tw-animate-css
pnpm dlx shadcn@latest init

# 2. Delete v3 config (v4 doesn't use it)
rm tailwind.config.ts

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') } }
})

components.json (CRITICAL):

{
  "tailwind": {
    "config": "",
    "css": "src/index.css",
    "baseColor": "slate",
    "cssVariables": true
  }
}

The Four-Step Architecture (MANDATORY)

Skipping steps breaks theming. Follow exactly:

Step 1: Define CSS Variables at Root

/* src/index.css */
@import "tailwindcss";
@import "tw-animate-css";

:root {
  --background: hsl(0 0% 100%);
  --foreground: hsl(222.2 84% 4.9%);
  --primary: hsl(221.2 83.2% 53.3%);
  --primary-foreground: hsl(210 40% 98%);
  /* ... all light mode colors with hsl() wrapper */
}

.dark {
  --background: hsl(222.2 84% 4.9%);
  --foreground: hsl(210 40% 98%);
  --primary: hsl(217.2 91.2% 59.8%);
  --primary-foreground: hsl(222.2 47.4% 11.2%);
  /* ... all dark mode colors */
}

Critical: Define at root level (NOT inside

@layer base
). Use
hsl()
wrapper.

Step 2: Map Variables to Tailwind

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  /* ... map ALL CSS variables */
}

Why: Generates utility classes (

bg-background
,
text-primary
). Without this, utilities don't exist.

Step 3: Apply Base Styles

@layer base {
  body {
    background-color: var(--background);
    color: var(--foreground);
  }
}

Critical: Reference variables directly. Never double-wrap:

hsl(var(--background))
.

Step 4: Result - Automatic Dark Mode

<div className="bg-background text-foreground">
  {/* Theme switches automatically via .dark class */}
</div>

No

dark:
variants needed for semantic colors.


Critical Rules

Always Do

  1. Wrap colors with
    hsl()
    in
    :root
    /
    .dark
    :
    --bg: hsl(0 0% 100%);
  2. Use
    @theme inline
    to map all CSS variables
  3. Set
    "tailwind.config": ""
    in components.json
  4. Delete
    tailwind.config.ts
    if exists
  5. Use
    @tailwindcss/vite
    plugin (NOT PostCSS)

Never Do

  1. Put
    :root
    /
    .dark
    inside
    @layer base
  2. Use
    .dark { @theme { } }
    (v4 doesn't support nested @theme)
  3. Double-wrap:
    hsl(var(--background))
  4. Use
    tailwind.config.ts
    for theme
  5. Use
    @apply
    with
    @layer base/components
    classes
  6. Use
    dark:
    variants for semantic colors

Common Errors & Solutions

Error 1: tw-animate-css Import

Error:

Cannot find module 'tailwindcss-animate'

# Wrong (v3 package)
npm install tailwindcss-animate

# Correct (v4 package)
pnpm add -D tw-animate-css
@import "tailwindcss";
@import "tw-animate-css";

Error 2: Colors Not Working

Error:

bg-primary
doesn't apply styles

Cause: Missing

@theme inline
mapping

@theme inline {
  --color-primary: var(--primary);
  /* Map ALL variables */
}

Error 3: Dark Mode Not Switching

Cause: Missing ThemeProvider

See

templates/theme-provider.tsx
and wrap your app.

Error 4: Build Fails

Error:

Unexpected config file

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

Error 5: @theme inline Breaks Multi-Theme

Cause:

@theme inline
bakes values at build time

Use

@theme
(without inline) for multi-theme systems:

/* For multi-theme (not just light/dark) */
@theme {
  --color-text-primary: var(--color-slate-900);
}

@layer theme {
  [data-theme="dark"] {
    --color-text-primary: var(--color-white);
  }
}

Error 6: @apply Breaking

Error:

Cannot apply unknown utility class

v4 changed

@apply
behavior:

/* Wrong (v3 pattern) */
@layer components {
  .custom-button { @apply px-4 py-2; }
}

/* Correct (v4 pattern) */
@utility custom-button {
  @apply px-4 py-2;
}

Error 7: @layer base Styles Ignored

Cause: CSS layer cascade issues

/* Better: Don't use @layer base for critical styles */
body {
  background-color: var(--background);
}

Quick Reference

SymptomCauseFix
bg-primary
doesn't work
Missing
@theme inline
Add mapping
Colors black/whiteDouble
hsl()
Use
var(--color)
not
hsl(var(--color))
Dark mode stuckMissing ThemeProviderWrap app
Build fails
tailwind.config.ts
exists
Delete file
Animation errorsWrong packageUse
tw-animate-css

Tailwind v4 New Features

OKLCH Color Space

v4 uses OKLCH for perceptually uniform colors. Automatic sRGB fallbacks generated.

@theme {
  /* Modern approach */
  --color-brand: oklch(0.7 0.15 250);
  
  /* Legacy (still works) */
  --color-brand: hsl(240 80% 60%);
}

Container Queries (Built-in)

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

Line Clamp (Built-in)

<p className="line-clamp-3">Truncate to 3 lines...</p>

Plugins

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

Migration from v3

Key Changes

  1. Delete
    tailwind.config.ts
  2. Move theme to CSS with
    @theme inline
  3. Replace
    tailwindcss-animate
    tw-animate-css
  4. Replace
    require()
    @plugin
  5. @apply
    in
    @layer components
    @utility

Color Migration

// Before: Hardcoded + dark variants
<div className="bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300">

// After: Semantic + automatic
<div className="bg-info/10 text-info">

Visual Changes

  • Ring width default: 3px → 1px (use
    ring-3
    to match v3)
  • Heading styles removed from Preflight (add via
    @tailwindcss/typography
    or custom)

Files

  • templates/index.css
    - Complete CSS with all variables
  • templates/theme-provider.tsx
    - Dark mode provider
  • templates/vite.config.ts
    - Vite configuration
  • templates/components.json
    - shadcn/ui v4 config
  • templates/utils.ts
    -
    cn()
    utility
  • references/architecture.md
    - Deep dive on four-step pattern
  • references/migration-guide.md
    - Semantic color migration
  • references/dark-mode.md
    - Complete dark mode setup

Setup Checklist

  • @tailwindcss/vite
    installed
  • vite.config.ts
    uses
    tailwindcss()
    plugin
  • components.json
    has
    "config": ""
  • NO
    tailwind.config.ts
    exists
  • src/index.css
    follows 4-step pattern
  • ThemeProvider wraps app
  • Theme toggle works

NEVER

  • Put
    :root
    or
    .dark
    inside
    @layer base
  • Use
    tailwind.config.ts
    with v4 (it's ignored)
  • Double-wrap colors:
    hsl(var(--background))
  • Use
    tailwindcss-animate
    (use
    tw-animate-css
    )
  • Use
    @apply
    on
    @layer base/components
    classes in v4
  • Skip the
    @theme inline
    step