jezweb/claude-skills8 files

Tailwind Theme Builder

Set up Tailwind v4 + shadcn/ui themed UI with dark mode. Install deps, configure CSS variables via @theme inline, wire dark mode toggle, verify. Use whenever the user mentions Tailwind v4, setting up Tailwind theming, shadcn/ui colours, dark mode, or troubleshooting colours not working, tw-animate-css errors, @theme inline conflicts, @apply breaking after upgrade, or v3 → v4 migration issues.

Specification
Skill ID
jezweb/claude-skills/tailwind-theme-builder
Publisher
jezweb
Repository
claude-skills
Installs
0
Files
8
Requires
claude-code-only
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

jezweb/claude-skills/tailwind-theme-builderInstalls these files
  • SKILL.md
  • assets/components.json
  • assets/index.css
  • assets/theme-provider.tsx
  • assets/tsconfig.app.json
  • assets/utils.ts
  • assets/vite.config.ts
  • references/migration-guide.md

What this skill tells the agent

Tailwind Theme Builder

Set up a fully themed Tailwind v4 + shadcn/ui project with dark mode. Produces configured CSS, theme provider, and working component library.

Architecture: The Four-Step Pattern

Tailwind v4 requires a specific architecture for CSS variable-based theming. This pattern is mandatory -- skipping or modifying steps breaks the theme.

How It Works

CSS Variable Definition --> @theme inline Mapping --> Tailwind Utility Class
--background           --> --color-background     --> bg-background
(with hsl() wrapper)      (references variable)     (generated class)

Dark mode switching:

ThemeProvider toggles .dark class on <html>
  --> CSS variables update automatically (.dark overrides :root)
  --> Tailwind utilities reference updated variables
  --> UI updates without re-render

Best Practices

  • Semantic names: Use --primary not --blue-500
  • Foreground pairing: Every background colour needs a foreground (--primary + --primary-foreground)
  • WCAG contrast: Normal text 4.5:1, large text 3:1, UI components 3:1
  • Chart colours: Use separate variables with @theme inline mapping, reference via var(--chart-1) in style props

Workflow

Step 1: Install Dependencies

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

# Delete v3 config if it exists
rm -f tailwind.config.ts

Step 2: Configure Vite

Copy assets/vite.config.ts or add the Tailwind plugin:

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

Step 3: Four-Step CSS Architecture (Mandatory)

This exact order is required. Skipping steps breaks the theme.

src/index.css:

@import "tailwindcss";
@import "tw-animate-css";

/* 1. Define CSS variables at root (NOT inside @layer base) */
: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 semantic tokens */
}

.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%);
}

/* 2. Map variables to Tailwind utilities */
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
}

/* 3. Apply base styles (NO hsl() wrapper here) */
@layer base {
  body {
    background-color: var(--background);
    color: var(--foreground);
  }
}

Result: bg-background, text-primary etc. work automatically. Dark mode switches via .dark class -- no dark: variants needed for semantic colours.

Step 4: Set Up Dark Mode

Copy assets/theme-provider.tsx to your components directory, then wrap your app:

import { ThemeProvider } from '@/components/theme-provider'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
    <App />
  </ThemeProvider>
)

Add a theme toggle -- install the dropdown menu then use the ModeToggle component below:

pnpm dlx shadcn@latest add dropdown-menu
// src/components/mode-toggle.tsx
import { Moon, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { useTheme } from "@/components/theme-provider"

export function ModeToggle() {
  const { setTheme } = useTheme()

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="icon">
          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme("light")}>Light</DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>Dark</DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>System</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Step 5: Configure components.json

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

"config": "" is critical -- v4 doesn't use tailwind.config.ts.


Critical Rules

Always:

  • Wrap colours with hsl() in :root/.dark
  • Use @theme inline to map all CSS variables
  • Use @tailwindcss/vite plugin (NOT PostCSS)
  • Delete tailwind.config.ts if it exists

Never:

  • Put :root/.dark inside @layer base
  • Use .dark { @theme { } } (v4 doesn't support nested @theme)
  • Double-wrap: hsl(var(--background))
  • Use @apply with @layer base classes (use @utility instead)

All 18 Gotchas

Quick Diagnosis

#SymptomCauseFix
1Variables ignored / theme broken:root inside @layer baseMove :root and .dark to root level
2Dark mode colours not switching.dark { @theme { } }Use CSS variables + single @theme inline
3Colours all black/whiteDouble hsl() wrappingUse var(--background) not hsl(var(...))
4bg-primary not generatedColours in tailwind.config.tsDelete config, use @theme inline
5bg-background class missingNo @theme inline blockAdd @theme inline mapping variables
6shadcn components breakcomponents.json has config pathSet "config": "" (empty string)
7Tailwind not processingUsing PostCSS pluginSwitch to @tailwindcss/vite plugin
8@/ imports failMissing path aliasesAdd paths to tsconfig.app.json
9Redundant dark: variantsUsing dark:bg-primary-darkJust use bg-primary -- variables handle it
10Hardcoded colours everywhereUsing bg-blue-600 dark:bg-blue-400Use semantic tokens: bg-primary
11Class merging bugsString concatenation for classesUse cn() from @/lib/utils
12Radix Select crashesEmpty string value value=""Use value="placeholder"
13Wrong Tailwind versionInstalled tailwindcss@^3Install tailwindcss@^4.1.0 + @tailwindcss/vite
14Missing peer depsOnly installed tailwindcssAlso install clsx, tailwind-merge, @types/node
15Broken in dark modeOnly tested light modeTest light, dark, system, and toggle transitions
16Fails WCAG contrastLooks fine visuallyCheck ratios: 4.5:1 normal text, 3:1 large/UI
17Build fails on animation importUsing tailwindcss-animate (deprecated)Use tw-animate-css or native CSS animations
18CSS priority issuesDuplicate @layer base after shadcn initMerge into single @layer base block

Gotcha Details with Code Examples

#1 -- :root inside @layer base

Tailwind v4 strips CSS outside @theme/@layer, but :root must be at root level to persist. This is the most common setup failure.

WRONG:

@layer base {
  :root { --background: hsl(0 0% 100%); }
}

CORRECT:

:root { --background: hsl(0 0% 100%); }
@layer base {
  body { background-color: var(--background); }
}

#2 -- Nested @theme

Tailwind v4 does not support @theme inside selectors. Use CSS variables in :root/.dark with a single @theme inline block.

WRONG:

More from jezweb

Use this skill in a project

Open any RiverX project, search the Skills panel for jezweb/claude-skills/tailwind-theme-builder, and install it in one click.

Use this skill