Marketplace tailwind-v4-shadcn

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

Tailwind v4 + shadcn/ui Production Stack

Production-tested: WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev) Last Updated: 2026-01-20 Versions: tailwindcss@4.1.18, @tailwindcss/vite@4.1.18 Status: Production Ready ✅


Quick Start (Follow This Exact Order)

# 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 if exists
rm tailwind.config.ts  # v4 doesn't use this file

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": "",              // ← Empty for v4
    "css": "src/index.css",
    "baseColor": "slate",
    "cssVariables": true
  }
}

The Four-Step Architecture (MANDATORY)

Skipping steps will break your theme. Follow exactly:

Step 1: Define CSS Variables at Root

/* src/index.css */
@import "tailwindcss";
@import "tw-animate-css";  /* Required for shadcn/ui animations */

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

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

Critical: Define at root level (NOT inside

@layer base
). Use
hsl()
wrapper.

Step 2: Map Variables to Tailwind Utilities

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

Why: Generates utility classes (

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

Step 3: Apply Base Styles

@layer base {
  body {
    background-color: var(--background);  /* NO hsl() wrapper here */
    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">
  {/* No dark: variants needed - theme switches automatically */}
</div>

Dark Mode Setup

1. Create ThemeProvider (see

templates/theme-provider.tsx
)

2. Wrap App:

// src/main.tsx
import { ThemeProvider } from '@/components/theme-provider'

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

3. Add Theme Toggle:

pnpm dlx shadcn@latest add dropdown-menu

See

reference/dark-mode.md
for ModeToggle component.


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
    (causes cascade issues)
  2. Use
    .dark { @theme { } }
    pattern (v4 doesn't support nested @theme)
  3. Double-wrap colors:
    hsl(var(--background))
  4. Use
    tailwind.config.ts
    for theme (v4 ignores it)
  5. Use
    @apply
    directive (deprecated in v4, see error #7)
  6. Use
    dark:
    variants for semantic colors (auto-handled)
  7. Use
    @apply
    with
    @layer base
    or
    @layer components
    classes (v4 breaking change - use
    @utility
    instead) | Source
  8. Wrap ANY styles in
    @layer base
    without understanding CSS layer ordering (see error #8) | Source

Common Errors & Solutions

This skill prevents 8 documented errors.

1. ❌ tw-animate-css Import Error

Error: "Cannot find module 'tailwindcss-animate'"

Cause: shadcn/ui deprecated

tailwindcss-animate
for v4.

Solution:

# ✅ DO
pnpm add -D tw-animate-css

# Add to src/index.css:
@import "tailwindcss";
@import "tw-animate-css";

# ❌ DON'T
npm install tailwindcss-animate  # v3 only

2. ❌ Colors Not Working

Error:

bg-primary
doesn't apply styles

Cause: Missing

@theme inline
mapping

Solution:

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

3. ❌ Dark Mode Not Switching

Error: Theme stays light/dark

Cause: Missing ThemeProvider

Solution:

  1. Create ThemeProvider (see
    templates/theme-provider.tsx
    )
  2. Wrap app in
    main.tsx
  3. Verify
    .dark
    class toggles on
    <html>
    element

4. ❌ Duplicate @layer base

Error: "Duplicate @layer base" in console

Cause: shadcn init adds

@layer base
- don't add another

Solution:

/* ✅ Correct - single @layer base */
@import "tailwindcss";

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

@theme inline { --color-background: var(--background); }

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

5. ❌ Build Fails with tailwind.config.ts

Error: "Unexpected config file"

Cause: v4 doesn't use

tailwind.config.ts
(v3 legacy)

Solution:

rm tailwind.config.ts

v4 configuration happens in

src/index.css
using
@theme
directive.


6. ❌ @theme inline Breaks Dark Mode in Multi-Theme Setups

Error: Dark mode doesn't switch when using

@theme inline
with custom variants (e.g.,
data-mode="dark"
) Source: GitHub Discussion #18560

Cause:

@theme inline
bakes variable VALUES into utilities at build time. When dark mode changes the underlying CSS variables, utilities don't update because they reference hardcoded values, not variables.

Why It Happens:

  • @theme inline
    inlines VALUES at build time:
    bg-primary
    background-color: oklch(...)
  • Dark mode overrides change the CSS variables, but utilities already have baked-in values
  • The CSS specificity chain breaks

Solution: Use

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

/* ✅ CORRECT - Use @theme without inline */
@custom-variant dark (&:where([data-mode=dark], [data-mode=dark] *));

@theme {
  --color-text-primary: var(--color-slate-900);
  --color-bg-primary: var(--color-white);
}

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

When to use inline:

  • Single theme + dark mode toggle (like shadcn/ui default) ✅
  • Referencing other CSS variables that don't change ✅

When NOT to use inline:

  • Multi-theme systems (data-theme="blue" | "green" | etc.) ❌
  • Dynamic theme switching beyond light/dark ❌

Maintainer Guidance (Adam Wathan):

"It's more idiomatic in v4 for the actual generated CSS to reference your theme variables. I would personally only use inline when things don't work without it."


7. ❌ @apply with @layer base/components (v4 Breaking Change)

Error:

Cannot apply unknown utility class: custom-button
Source: GitHub Discussion #17082

Cause: In v3, classes defined in

@layer base
and
@layer components
could be used with
@apply
. In v4, this is a breaking architectural change.

Why It Happens: v4 doesn't "hijack" the native CSS

@layer
at-rule anymore. Only classes defined with
@utility
are available to
@apply
.

Migration:

/* ❌ v3 pattern (worked) */
@layer components {
  .custom-button {
    @apply px-4 py-2 bg-blue-500;
  }
}

/* ✅ v4 pattern (required) */
@utility custom-button {
  @apply px-4 py-2 bg-blue-500;
}

/* OR use native CSS */
@layer base {
  .custom-button {
    padding: 1rem 0.5rem;
    background-color: theme(colors.blue.500);
  }
}

Note: This skill already discourages

@apply
usage. This error is primarily for users migrating from v3.


8. ❌ @layer base Styles Not Applying

Error: Styles defined in

@layer base
seem to be ignored Source: GitHub Discussion #16002 | Discussion #18123

Cause: v4 uses native CSS layers. Base styles CAN be overridden by utility layers due to CSS cascade if layers aren't explicitly ordered.

Why It Happens:

  • v3: Tailwind intercepted
    @layer base/components/utilities
    and processed them specially
  • v4: Uses native CSS layers - if you don't import layers in the right order, precedence breaks
  • Styles ARE being applied, but utilities override them

Solution Option 1: Define layers explicitly:

@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/base.css" layer(base);
@import "tailwindcss/components.css" layer(components);
@import "tailwindcss/utilities.css" layer(utilities);

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

Solution Option 2 (Recommended): Don't use

@layer base
- define styles at root level:

@import "tailwindcss";

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

body {
  background-color: var(--background); /* No @layer needed */
}

Applies to: ALL base styles, not just color variables. Avoid wrapping ANY styles in

@layer base
unless you understand CSS layer ordering.


Quick Reference

SymptomCauseFix
bg-primary
doesn't work
Missing
@theme inline
Add
@theme inline
block
Colors all black/whiteDouble
hsl()
wrapping
Use
var(--color)
not
hsl(var(--color))
Dark mode not switchingMissing ThemeProviderWrap app in
<ThemeProvider>
Build fails
tailwind.config.ts
exists
Delete file
Animation errorsUsing
tailwindcss-animate
Install
tw-animate-css

What's New in Tailwind v4

OKLCH Color Space (December 2024)

Tailwind v4.0 replaced the entire default color palette with OKLCH, a perceptually uniform color space. Source: Tailwind v4.0 Release | OKLCH Migration Guide

Why OKLCH:

  • Perceptual consistency: HSL's "50% lightness" is visually inconsistent across hues (yellow appears much brighter than blue at same lightness)
  • Better gradients: Smooth transitions without muddy middle colors
  • Wider gamut: Supports colors beyond sRGB on modern displays
  • More vibrant colors: Eye-catching, saturated colors previously limited by sRGB

Browser Support (January 2026):

  • Chrome 111+, Firefox 113+, Safari 15.4+, Edge 111+
  • Global coverage: 93.1%

Automatic Fallbacks: Tailwind generates sRGB fallbacks for older browsers:

.bg-blue-500 {
  background-color: #3b82f6; /* sRGB fallback */
  background-color: oklch(0.6 0.24 264); /* Modern browsers */
}

Custom Colors: When defining custom colors, OKLCH is now preferred:

@theme {
  /* Modern approach (preferred) */
  --color-brand: oklch(0.7 0.15 250);

  /* Legacy approach (still works) */
  --color-brand: hsl(240 80% 60%);
}

Migration: No breaking changes - Tailwind generates fallbacks automatically. For new projects, use OKLCH-aware tooling for custom colors.

Built-in Features (No Plugin Needed)

Container Queries (built-in as of v4.0):

<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 as of v3.3):

<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>

Removed Plugins:

  • @tailwindcss/container-queries
    - Built-in now
  • @tailwindcss/line-clamp
    - Built-in since v3.3

Tailwind v4 Plugins

Use

@plugin
directive (NOT
require()
or
@import
):

Typography (for Markdown/CMS content):

pnpm add -D @tailwindcss/typography
@import "tailwindcss";
@plugin "@tailwindcss/typography";
<article class="prose dark:prose-invert">{{ content }}</article>

Forms (cross-browser form styling):

pnpm add -D @tailwindcss/forms
@import "tailwindcss";
@plugin "@tailwindcss/forms";

Container Queries (built-in, no plugin needed):

<div className="@container">
  <div className="@md:text-lg">Responds to container width</div>
</div>

Common Plugin Errors:

/* ❌ WRONG - v3 syntax */
@import "@tailwindcss/typography";

/* ✅ CORRECT - v4 syntax */
@plugin "@tailwindcss/typography";

Setup 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

File Templates

Available in

templates/
directory:

  • index.css - Complete CSS with all color variables
  • components.json - shadcn/ui v4 config
  • vite.config.ts - Vite + Tailwind plugin
  • theme-provider.tsx - Dark mode provider
  • utils.ts -
    cn()
    utility

Migration from v3

See

reference/migration-guide.md
for complete guide.

Key Changes:

  • Delete
    tailwind.config.ts
  • Move theme to CSS with
    @theme inline
  • Replace
    @tailwindcss/line-clamp
    (now built-in:
    line-clamp-*
    )
  • Replace
    tailwindcss-animate
    with
    tw-animate-css
  • Update plugins:
    require()
    @plugin

Additional Migration Gotchas

Automated Migration Tool May Fail

Warning: The

@tailwindcss/upgrade
utility often fails to migrate configurations. Source: Community Reports | GitHub Discussion #16642

Common failures:

  • Typography plugin configurations
  • Complex theme extensions
  • Custom plugin setups

Recommendation: Don't rely on automated migration. Follow manual steps in the migration guide instead.

Default Element Styles Removed

Tailwind v4 takes a more minimal approach to Preflight, removing default styles for headings, lists, and buttons. Source: GitHub Discussion #16517 | Medium: Migration Problems

Impact:

  • All headings (
    <h1>
    through
    <h6>
    ) render at same size
  • Lists lose default padding
  • Visual regressions in existing projects

Solutions:

Option 1: Use @tailwindcss/typography for content pages:

pnpm add -D @tailwindcss/typography
@import "tailwindcss";
@plugin "@tailwindcss/typography";
<article className="prose dark:prose-invert">
  {/* All elements styled automatically */}
</article>

Option 2: Add custom base styles:

@layer base {
  h1 { @apply text-4xl font-bold mb-4; }
  h2 { @apply text-3xl font-bold mb-3; }
  h3 { @apply text-2xl font-bold mb-2; }
  ul { @apply list-disc pl-6 mb-4; }
  ol { @apply list-decimal pl-6 mb-4; }
}

PostCSS Setup Complexity

Recommendation: Use

@tailwindcss/vite
plugin for Vite projects instead of PostCSS. Source: Medium: Migration Problems | GitHub Discussion #15764

Why Vite Plugin is Better:

// ✅ Vite Plugin - One line, no PostCSS config
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})

// ❌ PostCSS - Multiple steps, plugin compatibility issues
// 1. Install @tailwindcss/postcss
// 2. Configure postcss.config.js
// 3. Manage plugin order
// 4. Debug plugin conflicts

PostCSS Problems Reported:

  • Error: "It looks like you're trying to use tailwindcss directly as a PostCSS plugin"
  • Multiple PostCSS plugins required:
    postcss-import
    ,
    postcss-advanced-variables
    ,
    tailwindcss/nesting
  • v4 PostCSS plugin is separate package:
    @tailwindcss/postcss

Official Guidance: The Vite plugin is recommended for Vite projects. PostCSS is for legacy setups or non-Vite environments.

Visual Changes

Ring Width Default: Changed from 3px to 1px Source: Medium: Migration Guide

  • ring
    class is now thinner
  • Use
    ring-3
    to match v3 appearance
// v3: 3px ring
<button className="ring">Button</button>

// v4: 1px ring (thinner)
<button className="ring">Button</button>

// Match v3 appearance
<button className="ring-3">Button</button>

Reference Documentation

  • architecture.md - Deep dive into 4-step pattern
  • dark-mode.md - Complete dark mode implementation
  • common-gotchas.md - Troubleshooting guide
  • migration-guide.md - v3 → v4 migration

Official Documentation


Last Updated: 2026-01-20 Skill Version: 3.0.0 Tailwind v4: 4.1.18 (Latest) Production: WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev)

Changelog:

  • v3.0.0 (2026-01-20): Major research update - added 3 TIER 1 errors (#6-8), expanded migration guide with community findings (TIER 2), added OKLCH color space section, PostCSS complexity warnings, and migration tool limitations
  • v2.0.1 (2026-01-03): Production verification
  • v2.0.0: Initial release with 5 documented errors