tailwind-4

Tailwind CSS 4 patterns and best practices. Trigger: When styling with Tailwind classes, especially when dynamic styling or CSS variables are involved (no var() in class names).

SKILL.md

GitHub

Styling Decision Tree

Tailwind class exists?  → class="..."
Dynamic value?          → style attribute (e.g., style="width: ${x}%")
Conditional styles?     → Use framework utility (cn(), clsx, etc.)
Static only?            → class="..." (no utility needed)
Library can't use class?→ style attribute with var() constants

Tailwind v4 @theme Block Rules

CRITICAL: Only One @theme Block Per Project

Tailwind v4 only processes ONE @theme block. If you have multiple @theme blocks in different CSS files, only the last loaded one takes effect, and variables from earlier blocks are completely ignored.

Problem:

/* ❌ WRONG: Multiple @theme blocks */
/* src/styles/theme.css */
@theme {
  --color-my-blue: #0862C8;
  --color-my-cyan: #2AD1C9;
}

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

@theme {
  --font-monda: 'Monda', sans-serif;
}

Result:

  • --color-my-blue and --color-my-cyan from theme.css are ignored
  • Only --font-monda from global.css is active
  • Classes like bg-my-blue, text-my-cyan don't exist
  • Tailwind falls back to default values (white background, black text)

Solution:

/* ✅ CORRECT: Single consolidated @theme block */
/* src/styles/global.css */
@import "tailwindcss";

@theme {
  /* All colors and fonts in ONE place */
  --color-my-blue: #0862C8;
  --color-my-cyan: #2AD1C9;
  --font-monda: 'Monda', sans-serif;
}

How to Fix Multiple @theme Blocks

  1. Remove all extra @theme blocks from CSS files except the main one
  2. Consolidate ALL @theme variables into the main @theme block (usually in global.css or main entry CSS file)
  3. Delete unused CSS files that contained @theme blocks
  4. Rebuild project with npm run build
  5. Verify colors work by checking compiled CSS for your custom color variables

Detection Pattern

If you see these symptoms:

  • Custom colors rendering as white/black instead of defined values
  • Classes with your custom color names not working
  • CSS variables exist in source but not in compiled output

Diagnose:

# Check for multiple @theme blocks
grep -r "@theme" src/styles/ --include="*.css"

# Check if your colors are in compiled CSS
grep -o "your-color-name" dist/_astro/*.css

Fix: Consolidate all @theme variables into single block in main CSS file.

Critical Rules

Never Use var() in class Names

<!-- ❌ NEVER: var() in class names -->
<div class="bg-[var(--color-primary)]"></div>
<div class="text-[var(--text-color)]"></div>

<!-- ✅ ALWAYS: Use Tailwind semantic classes -->
<div class="bg-primary"></div>
<div class="text-dark-400"></div>

Never Use Hex Colors

<!-- ❌ NEVER: Hex colors in class names -->
<p class="text-[#ffffff]"></p>
<div class="bg-[#1e293b]"></div>

<!-- ✅ ALWAYS: Use Tailwind color classes -->
<p class="text-white"></p>
<div class="bg-dark-800"></div>

Conditional Classes

For conditional class logic, use class-merging utilities like clsx or tailwind-merge (often combined as cn()).

When to Use Class Merging

<!-- ✅ Conditional classes - use utility -->
<div class="base-class active-class"></div>

<!-- ✅ Multiple conditions -->
<div class="rounded-lg border bg-blue-500 text-white"></div>

When NOT to Use Class Merging

<!-- ❌ Static classes - no utility needed -->
<div class="flex items-center gap-2"></div>

<!-- ✅ Just use class directly -->
<div class="flex items-center gap-2"></div>

Common Utility Implementation

import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs) {
  return twMerge(clsx(inputs));
}

Style Constants for Libraries with Limited Class Support

When libraries don't accept class names or have limited styling options, use CSS custom properties:

// ✅ Constants with var() - ONLY for library props that can't use classes
const CHART_COLORS = {
  primary: "var(--color-primary)",
  secondary: "var(--color-secondary)",
  text: "var(--color-text)",
  gridLine: "var(--color-border)",
};

// Usage depends on the library's API
// Example: Chart.js configuration object
const chartConfig = {
  scales: {
    y: {
      grid: { color: CHART_COLORS.gridLine }
    }
  }
};

Dynamic Values

For truly dynamic values, use inline styles:

<!-- ✅ style attribute for dynamic values -->
<div style="width: 75%"></div>
<div style="opacity: 0.5"></div>

<!-- ✅ CSS custom properties for theming -->
<div style="--progress: 75%"></div>

Common Patterns

Flexbox

<div class="flex items-center justify-between gap-4"></div>
<div class="flex flex-col gap-2"></div>
<div class="inline-flex items-center"></div>

Grid

<div class="grid grid-cols-3 gap-4"></div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"></div>

Spacing

<!-- Padding -->
<div class="p-4"></div>           <!-- All sides -->
<div class="px-4 py-2"></div>     <!-- Horizontal, vertical -->
<div class="pt-4 pb-2"></div>     <!-- Top, bottom -->

<!-- Margin -->
<div class="m-4"></div>
<div class="mx-auto"></div>       <!-- Center horizontally -->
<div class="mt-8 mb-4"></div>

Typography

<h1 class="text-2xl font-bold text-white"></h1>
<p class="text-sm text-dark-400"></p>
<span class="text-xs font-medium uppercase tracking-wide"></span>

Borders & Shadows

<div class="rounded-lg border border-dark-700"></div>
<div class="rounded-full shadow-lg"></div>
<div class="ring-2 ring-blue-500 ring-offset-2"></div>

States

<button class="hover:bg-blue-600 focus:ring-2 active:scale-95"></button>
<input class="focus:border-blue-500 focus:outline-none">
<div class="group-hover:opacity-100"></div>

Responsive

<div class="w-full md:w-1/2 lg:w-1/3"></div>
<div class="hidden md:block"></div>
<div class="text-sm md:text-base lg:text-lg"></div>

Dark Mode

<div class="bg-white dark:bg-dark-900"></div>
<p class="text-gray-900 dark:text-white"></p>

Arbitrary Values (Escape Hatch)

<!-- ✅ OK for one-off values not in design system -->
<div class="w-[327px]"></div>
<div class="top-[117px]"></div>
<div class="grid-cols-[1fr_2fr_1fr]"></div>

<!-- ❌ Don't use for colors - use theme instead -->
<div class="bg-[#1e293b]"></div>  <!-- NO -->