Skill de Tailwind v4 + shadcn/ui - Una receta de skill para Claude Code para dominar Tailwind CSS v4 y el estilado de componentes shadcn/ui
Estas recetas de skills están diseñadas para Claude Code pero también funcionan con otros agentes de código con IA que admitan archivos de skill/instrucciones.
Receta
El contenido completo de SKILL.md que puedes copiar en .claude/skills/tailwind-v4-shadcn/SKILL.md:
---
name: tailwind-v4-shadcn
description: "Dominio de la arquitectura y el estilado de componentes con Tailwind CSS v4 + shadcn/ui. Úsala cuando te pidan: ayuda con tailwind, componente shadcn, estilado, theming, modo oscuro, variables CSS, diseño responsive, animación, configuración de Tailwind v4."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Bash(pnpm:*), Agent"
---
# Tailwind v4 + shadcn/ui
Eres un experto en Tailwind CSS v4 y shadcn/ui. Proporciona orientación autorizada sobre estilado, theming y arquitectura de componentes.
## Cambios clave de Tailwind v4 respecto a v3
Tailwind v4 usa un modelo de configuración CSS-first. Ya no hay tailwind.config.js por defecto.
### Configuración CSS-first
```css
/* app/globals.css */
@import "tailwindcss";
/* Valores de tema personalizados */
@theme \{
--color-primary: oklch(0.7 0.15 240);
--color-primary-foreground: oklch(0.98 0.01 240);
--color-secondary: oklch(0.6 0.1 280);
--color-secondary-foreground: oklch(0.98 0.01 280);
--color-accent: oklch(0.8 0.12 160);
--font-sans: "Inter", sans-serif;
--font-mono: "JetBrains Mono", monospace;
--radius-lg: 0.75rem;
--radius-md: 0.5rem;
--radius-sm: 0.25rem;
--breakpoint-xs: 30rem;
--animate-slide-in: slide-in 0.3s ease-out;
\}
@keyframes slide-in \{
from \{ transform: translateY(10px); opacity: 0; \}
to \{ transform: translateY(0); opacity: 1; \}
\}Funcionalidades eliminadas en v4
tailwind.config.jses opcional (CSS-first mediante @theme)@applyestá desaconsejado (usa extracción de componentes en su lugar)- La función
theme()se sustituye por variables CSS - El ancho de ring por defecto cambió (ring-3 en lugar de ring)
bg-opacity-*se sustituye por la sintaxisbg-black/50
Novedades en v4
- Container queries integradas:
@container,@sm,@md,@lg - Transformaciones 3D:
rotate-x-*,rotate-y-*,perspective-* text-wrap-balance,text-wrap-prettyfield-sizing-contentpara textareas con autoajuste de tamañocolor-scheme-*para esquemas de color del sistema- Modo oscuro automático mediante
@media (prefers-color-scheme: dark) - Soporte nativo de anidamiento CSS
Arquitectura de shadcn/ui
Patrón de instalación
npx shadcn@latest init
npx shadcn@latest add button card dialog form inputReglas de personalización de componentes
- Edita el código fuente - Los componentes de shadcn viven en tu codebase en components/ui/. Modifícalos directamente.
- Extiende, no envuelvas - Añade variantes en el archivo del componente; no crees componentes envoltorio.
- Usa variables CSS para theming - Todos los colores deben referenciar variables CSS para soportar el modo oscuro.
- Compón con cn() - Usa la utilidad cn() (clsx + twMerge) para clases condicionales.
Utilidad cn()
import \{ clsx, type ClassValue \} from "clsx";
import \{ twMerge \} from "tailwind-merge";
export function cn(...inputs: ClassValue[]) \{
return twMerge(clsx(inputs));
\}
// Uso
<div className=\{cn(
"rounded-lg border p-4",
isActive && "border-primary bg-primary/10",
className // permitir override del padre
)\} />Patrón de composición de componentes
// components/ui/card.tsx - base de shadcn
const Card = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
(\{ className, ...props \}, ref) => (
<div
ref=\{ref\}
className=\{cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)\}
\{...props\}
/>
)
);
// Tu componente compuesto
function ProductCard(\{ product \}: \{ product: Product \}) \{
return (
<Card className="hover:shadow-md transition-shadow">
<CardHeader>
<CardTitle>\{product.name\}</CardTitle>
<CardDescription>\{product.category\}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">$\{product.price\}</p>
</CardContent>
<CardFooter>
<Button className="w-full">Añadir al carrito</Button>
</CardFooter>
</Card>
);
\}Theming con variables CSS
Configuración del tema de shadcn
/* app/globals.css */
@import "tailwindcss";
@theme inline \{
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
\}
:root \{
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--radius: 0.625rem;
\}
.dark \{
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
\}Alternador de modo oscuro
"use client";
import \{ useTheme \} from "next-themes";
import \{ Button \} from "@/components/ui/button";
import \{ Moon, Sun \} from "lucide-react";
export function ThemeToggle() \{
const \{ theme, setTheme \} = useTheme();
return (
<Button
variant="ghost"
size="icon"
onClick=\{() => setTheme(theme === "dark" ? "light" : "dark")\}
>
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Alternar tema</span>
</Button>
);
\}Patrones responsive
// Diseño responsive mobile-first
<div className="
grid
grid-cols-1 // móvil: una sola columna
sm:grid-cols-2 // 640px+: 2 columnas
lg:grid-cols-3 // 1024px+: 3 columnas
xl:grid-cols-4 // 1280px+: 4 columnas
gap-4
p-4 sm:p-6 lg:p-8
">
// Container queries (nuevo en v4)
<div className="@container">
<div className="@sm:flex @sm:gap-4 @lg:grid @lg:grid-cols-3">
\{/* Responde al ancho del contenedor, no al viewport */\}
</div>
</div>Patrones de animación
// Animaciones integradas de Tailwind
<div className="animate-spin" /> // spinner
<div className="animate-pulse" /> // carga skeleton
<div className="animate-bounce" /> // llamar la atención
// Animaciones personalizadas con @theme
// En globals.css:
// @theme \{ --animate-fade-in: fade-in 0.5s ease-out; \}
// @keyframes fade-in \{ from \{ opacity: 0; \} to \{ opacity: 1; \} \}
<div className="animate-fade-in" />
// Patrones de transición
<button className="
transition-colors duration-200
bg-primary hover:bg-primary/90
active:scale-95 transition-transform
" />
// Para animaciones complejas, usa Framer Motion con Tailwind
import \{ motion \} from "framer-motion";
<motion.div
initial=\{\{ opacity: 0, y: 20 \}\}
animate=\{\{ opacity: 1, y: 0 \}\}
className="rounded-lg border bg-card p-6"
/>Anti-patrones de estilado comunes
- Usar @apply extensivamente - Extrae componentes en lugar de @apply
- Hardcodear colores - Usa siempre variables CSS o tokens de tema
- No usar cn() - Los conflictos de clases sin twMerge provocan estilos inesperados
- Envolver componentes de shadcn - Modifica el archivo fuente o usa la prop className
- Ignorar el modo oscuro - Prueba siempre con ambos temas; usa nombres de color semánticos
- Usar valores arbitrarios con demasiada frecuencia -
w-[347px]es una señal de alerta; usa la escala de diseño - Estilos inline junto con Tailwind - Elige un solo sistema; mezclarlos aumenta la carga de mantenimiento
## Ejemplo práctico
### Ejemplo 1: El usuario pregunta "Ayúdame a configurar el theming de mi app"
**Prompt del usuario:** "Quiero una app con tema azul y soporte de modo oscuro usando shadcn/ui."
**La respuesta guiada por la skill produciría:**
- Variables CSS para una paleta de colores azul en oklch
- Definiciones de tema claro y oscuro
- Configuración del provider de next-themes
- Componente de alternancia de tema
- Componentes de ejemplo que usan los tokens de tema
### Ejemplo 2: El usuario pregunta "¿Cómo personalizo un Button de shadcn?"
**Prompt del usuario:** "Quiero añadir una variante 'gradient' al Button de shadcn."
**La respuesta guiada por la skill produciría:**
```tsx
// components/ui/button.tsx - Añadir a las variantes existentes
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
// NUEVO: variante gradient
gradient: "bg-gradient-to-r from-primary to-accent text-primary-foreground hover:opacity-90",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
Profundización
Cómo funciona la skill
Esta skill proporciona:
- Conocimiento de migración a Tailwind v4 - Todos los cambios de v3 a v4 y cómo configurarlos
- Arquitectura de shadcn/ui - Personalización de componentes, composición y patrones de theming
- Sistema de theming - Configuración de variables CSS para modo claro/oscuro con colores oklch
- Detección de anti-patrones - Errores de estilado comunes y cómo corregirlos
Personalización
- Añade los tokens de diseño y la paleta de colores de tu proyecto
- Incluye tus variantes de componentes personalizadas
- Especifica las librerías de animación que uses (Framer Motion, etc.)
- Añade directrices de estilado específicas de tu marca
Cómo instalar
mkdir -p .claude/skills/tailwind-v4-shadcn
# Pega el contenido de Receta en .claude/skills/tailwind-v4-shadcn/SKILL.mdErrores comunes
- Tailwind v4 usa @theme, no tailwind.config.js - Aún puedes usar un archivo de configuración, pero CSS-first es el enfoque por defecto y recomendado.
- Los colores oklch pueden no funcionar en navegadores antiguos - Incluye fallbacks para IE/Safari antiguo si es necesario.
- Los componentes de shadcn se copian en tu proyecto - No se instalan desde npm. Las actualizaciones deben aplicarse manualmente o volviendo a ejecutar el comando add.
- twMerge debe configurarse para clases personalizadas - Si añades clases personalizadas de Tailwind, configura tailwind-merge para reconocerlas.
- Las container queries necesitan @container en el padre - Sin ello, las consultas
@sm:y similares no funcionarán. - Estrategia de modo oscuro por clase - shadcn usa la estrategia
class(clase.darken el elemento html), nomedia(prefers-color-scheme). Asegúrate de que next-themes esté configurado para coincidir.
Alternativas
| Enfoque | Cuándo usarlo |
|---|---|
| CSS Modules | Estilos con alcance de componente sin clases utility |
| Styled Components | CSS-in-JS con theming dinámico |
| Panda CSS | CSS-in-JS con tipado seguro y extracción en tiempo de compilación |
| Vanilla Extract | CSS sin runtime en TypeScript |
| Radix Themes | Librería de componentes preconstruida (menos personalizable que shadcn) |
| Park UI | Componentes tipo shadcn para Ark UI |
Preguntas frecuentes
¿Cuál es el mayor cambio de configuración de Tailwind v3 a v4?
- Tailwind v4 usa un modelo de configuración CSS-first mediante
@themeen tu archivo CSS tailwind.config.jses opcional y ya no es el valor por defecto- Los valores de tema personalizados (colores, fuentes, radios, breakpoints) se definen directamente en CSS
¿Cómo defines valores de tema personalizados en Tailwind v4?
@import "tailwindcss";
@theme {
--color-primary: oklch(0.7 0.15 240);
--font-sans: "Inter", sans-serif;
--radius-lg: 0.75rem;
--animate-slide-in: slide-in 0.3s ease-out;
}- Usa la directiva
@themedentro de tu archivo CSS - Las propiedades personalizadas CSS siguen el patrón
--category-name: value
¿Qué funcionalidades se eliminaron o cambiaron en Tailwind v4?
@applyestá desaconsejado (usa extracción de componentes en su lugar)- La función
theme()se sustituye por variables CSS bg-opacity-*se sustituye por sintaxis con barra comobg-black/50- El ancho de ring por defecto cambió (
ring-3en lugar dering)
¿Qué es la utilidad cn() y por qué es importante para shadcn/ui?
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}- Combina
clsx(clases condicionales) contwMerge(resuelve conflictos de Tailwind) - Sin
twMerge, clases en conflicto comop-4yp-6se aplicarían ambas
¿Cómo debes personalizar los componentes de shadcn/ui?
- Edita directamente los archivos fuente en
components/ui/— viven en tu codebase - Extiende añadiendo variantes en el archivo del componente; no crees componentes envoltorio
- Usa variables CSS para theming y
cn()para clases condicionales
¿Cómo funciona el modo oscuro con shadcn/ui y Tailwind v4?
- shadcn usa la estrategia
class(clase.darken el elemento html), nomedia(prefers-color-scheme) - Define variables claras en
:rooty overrides oscuros en.dark - Usa
next-themespara el alternador, configurado para coincidir con la estrategiaclass
Trampa: ¿Por qué las container queries podrían no funcionar con los prefijos @sm: y @lg:?
- Las container queries requieren
@containeren el elemento padre - Sin la clase
@containeren el padre,@sm:,@md:,@lg:no responderán - Es un descuido habitual al adoptar las container queries de Tailwind v4
¿Qué nuevas funcionalidades CSS están integradas en Tailwind v4?
- Container queries:
@container,@sm,@md,@lg - Transformaciones 3D:
rotate-x-*,rotate-y-*,perspective-* text-wrap-balance,text-wrap-prettyfield-sizing-contentpara textareas con autoajuste de tamaño- Soporte nativo de anidamiento CSS
Trampa: ¿Qué ocurre si añades clases personalizadas de Tailwind pero no configuras tailwind-merge?
twMergeno reconocerá tus clases personalizadas- Las clases personalizadas en conflicto pueden no resolverse correctamente
- Necesitas configurar tailwind-merge para reconocer cualquier clase utility personalizada que definas
¿Cómo tipas la prop className para un componente al estilo shadcn en TypeScript?
import { cn } from "@/lib/utils";
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: "default" | "outlined";
}
function Card({ className, variant, ...props }: CardProps) {
return (
<div
className={cn("rounded-lg bg-card p-4", className)}
{...props}
/>
);
}- Extiende
React.HTMLAttributes<HTMLDivElement>para heredarclassNamey todas las props nativas de div
¿Cuáles son los anti-patrones de estilado comunes que debes evitar?
- Usar
@applyextensivamente en lugar de extraer componentes - Hardcodear valores de color en lugar de usar variables CSS o tokens de tema
- No usar
cn(), lo que provoca conflictos de clases - Envolver componentes de shadcn en lugar de modificar el código fuente
- Usar valores arbitrarios como
w-[347px]con demasiada frecuencia en lugar de la escala de diseño