---
name: tailwind-experto
description: Tailwind CSS v4 mejores prácticas. Cubre configuración CSS-first con @theme y @custom-variant, design tokens en Tailwind, patrones de componentes, responsive con breakpoints, dark mode, plugins custom, compilación JIT y anti-patrones como utility soup y @apply abuse.
version: "1.0.0"
herramientasPermitidas: [Read]
exclusiones:
  - "No cargar para CSS nativo moderno (Container Queries, @layer, Nesting, View Transitions) — para CSS nativo cargar `css-moderno`."
  - "No cargar para accesibilidad (ARIA, contraste WCAG, navegación por teclado) — para accesibilidad cargar `accesibilidad-a11y`."
  - "No cargar para Tailwind v3 con `tailwind.config.js` — la configuración CSS-first de v4 es incompatible con v3."
  - "No cargar para CSS-in-JS (styled-components, Emotion) o Bootstrap/MUI — son ecosistemas distintos sin utilidades atómicas."
evolvable: true  # default para skill estandar
---
# Tailwind CSS v4 Experto

## Cuándo NO cargar

- El trabajo es CSS nativo moderno: Container Queries, `@layer`, Nesting, View Transitions — cargar `css-moderno`.
- El tema es accesibilidad: ARIA, contraste WCAG, focus trap, navegación por teclado — cargar `accesibilidad-a11y`.
- El proyecto usa Tailwind v3 con `tailwind.config.js` — la configuración CSS-first con `@theme` es específica de v4 e incompatible con v3.
- El proyecto usa CSS-in-JS, Bootstrap o Material UI sin Tailwind — son ecosistemas distintos.

## Cambio Fundamental en v4

Tailwind v4 abandona el archivo `tailwind.config.js` en favor de **configuración
directa en CSS**. No más archivo de configuración JavaScript — todo se define
con `@theme` y directivas CSS nativas.

```css
/* app/globals.css — la config ahora está aquí */
@import "tailwindcss";

@theme {
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;

  --color-primario: oklch(56% 0.2 250);
  --color-primario-oscuro: oklch(46% 0.2 250);
  --color-secundario: oklch(70% 0.15 160);

  --radius-sm: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
  --radius-xl: 1rem;

  --spacing-18: 4.5rem;
  --spacing-88: 22rem;
}
```

Para tokens de color completos, tipografía fluida y escalas, ver [recursos/referencia-completa.md](recursos/referencia-completa.md).

---

## @custom-variant

Define variantes personalizadas sin plugin:

```css
/* Variante para elementos con data attribute */
@custom-variant data-activo (&[data-activo='true']);
@custom-variant data-disabled (&[data-disabled='true']);
@custom-variant data-error (&[data-error='true']);

/* Uso en HTML */
/* <div data-activo="true" class="data-activo:bg-marca-100 data-activo:border-marca-500"> */
```

```css
/* Variante para grupos custom */
@custom-variant sidebar-collapsed (.sidebar-collapsed &);

/* Uso */
/* <nav class="w-64 sidebar-collapsed:w-16 transition-all"> */
```

---

## Patrones de Componentes

### Patrón: clases base + variantes (cva)

```tsx
// components/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';

const buttonVariants = cva(
  // Clases base siempre presentes
  'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-marca-500 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variante: {
        primario: 'bg-marca-500 text-white hover:bg-marca-600 active:bg-marca-700',
        secundario: 'bg-transparent border border-marca-500 text-marca-600 hover:bg-marca-50',
        ghost: 'hover:bg-gray-100 text-gray-700',
        destructivo: 'bg-error text-white hover:bg-red-700',
      },
      tamano: {
        sm: 'h-8 px-3 text-sm',
        md: 'h-10 px-4 text-base',
        lg: 'h-12 px-6 text-lg',
      },
    },
    defaultVariants: {
      variante: 'primario',
      tamano: 'md',
    },
  }
);

export function Button({ variante, tamano, className, ...props }: ButtonProps) {
  return (
    <button className={buttonVariants({ variante, tamano, className })} {...props} />
  );
}
```

Para patrones adicionales (layouts, tarjetas, plugins custom), ver [recursos/referencia-completa.md](recursos/referencia-completa.md).

---

## Responsive con Breakpoints

### Sistema de breakpoints (v4 por defecto)

| Prefijo | Ancho mínimo | Dispositivo típico |
|---------|-------------|-------------------|
| `sm:`   | 640px       | Móvil grande |
| `md:`   | 768px       | Tablet |
| `lg:`   | 1024px      | Laptop |
| `xl:`   | 1280px      | Desktop |
| `2xl:`  | 1536px      | Pantalla grande |

### Mobile-first (siempre)

```html
<!-- Correcto: móvil base, desktop override -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">

<!-- MAL: desktop base, móvil override (requiere sobreescribir) -->
<div class="grid grid-cols-3 max-md:grid-cols-1">
```

### Breakpoints personalizados en v4

```css
@theme {
  --breakpoint-xs:  475px;
  --breakpoint-3xl: 1920px;
}
```

---

## Dark Mode en v4

```css
/* globals.css */
@import "tailwindcss";

@theme {
  --color-fondo: #ffffff;
  --color-texto: #0f172a;
}

@media (prefers-color-scheme: dark) {
  @theme {
    --color-fondo: #0f172a;
    --color-texto: #f1f5f9;
  }
}
```

```tsx
// Dark mode con clase manual (toggle)
<html className={tema === 'oscuro' ? 'dark' : ''}>

// En el CSS
@custom-variant dark (&:where(.dark, .dark *));
```

---

## Anti-patrones

### Utility Soup — demasiadas clases en un elemento

```html
<!-- MAL: ilegible, difícil de mantener -->
<button class="inline-flex items-center justify-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 active:bg-blue-800 disabled:cursor-not-allowed disabled:opacity-50">

<!-- BIEN: extraer componente con cva() -->
<Button variante="primario" tamano="sm">Guardar</Button>
```

### @apply Abuse — recrear Sass con Tailwind

```css
/* MAL: el propósito de @apply es crear un nuevo .scss, no reutilizar utilidades */
.card {
  @apply rounded-lg border border-gray-200 bg-white p-6 shadow-sm;
}

/* BIEN: si es reutilizable, hacer componente React/Vue/Angular */
/* Si es un patrón CSS puro, usar custom property o @layer */
@layer componentes {
  .card {
    border-radius: var(--radius-lg);
    border: 1px solid var(--color-borde);
    background: var(--color-superficie);
    padding: var(--spacing-6);
    box-shadow: var(--shadow-sm);
  }
}
```

### Clases condicionales con interpolación de string

```tsx
// MAL: Tailwind JIT no puede detectar clases dinámicas construidas con interpolación
const color = 'azul';
<div className={`bg-${color}-500`}> // 'bg-azul-500' no existe en el output

// BIEN: objeto de mapeo completo
const colorMap = {
  azul:  'bg-blue-500',
  rojo:  'bg-red-500',
  verde: 'bg-green-500',
} as const;
<div className={colorMap[color]}>
```

## Gotchas / Errores comunes no obvios

**En Tailwind v4, las variables de `@theme` no están disponibles en JavaScript**: a diferencia de v3 donde `tailwind.config.js` exporta los tokens directamente, en v4 los tokens son variables CSS (`--color-primario`) que solo viven en el CSS — no se pueden importar en componentes JavaScript para comparar valores programáticamente. Causa: el diseño CSS-first de v4 es intencional. Fix: para usar tokens en JavaScript (ej: en una librería de gráficas), leer las variables CSS en runtime con `getComputedStyle(document.documentElement).getPropertyValue('--color-primario')`.

**`@apply` con clases que incluyen variantes responsivas (`md:flex`) lanza error en Tailwind v4**: `@apply md:flex` dentro de un bloque CSS no es válido porque las variantes responsivas en v4 son media queries generadas — no se pueden aplicar con `@apply`. Causa: `@apply` solo funciona con clases base, no con variantes de contexto como `md:`, `hover:` o `dark:`. Fix: escribir la media query CSS nativa directamente (`@media (min-width: theme(--breakpoint-md)) { display: flex; }`) o usar el patrón de componente con `cva()`.

**La purga de clases de Tailwind v4 no detecta clases en archivos `.py`, `.rb` o templates de servidor no configurados**: si el backend genera HTML dinámicamente con strings de clases Tailwind, el compilador de v4 no las ve y las elimina del CSS final. Causa: la detección de contenido de v4 escanea archivos según la configuración de `content` — los templates de servidor no configurados están excluidos. Fix: agregar los paths de los templates al scanner de contenido en el `@import "tailwindcss"` con `source()` o en la config, o usar la directiva `@source` en el CSS.

**`clsx` o `cn()` con objetos condicionales y clases que se superponen no garantiza el orden correcto**: `cn('p-4', condicion && 'p-8')` puede resultar en `p-4 p-8` en el HTML, y el navegador aplica la última clase declarada en el CSS — que es la que aparece último en la hoja de estilos de Tailwind, no la última en el string. Causa: la cascada CSS depende del orden de declaración en la hoja de estilos, no del orden en el atributo `class`. Fix: usar `twMerge` (o `cn` basado en `tailwind-merge`) que resuelve conflictos de clases del mismo grupo utilitario correctamente.
