Arma un proyecto Next.js 15 con Tailwind CSS v4 y shadcn/ui - la pila exacta usada por este cookbook. App Router, React 19, TypeScript strict mode, Tailwind configurado vía CSS (no JS), y componentes shadcn que posees y editas directamente en components/ui/.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
# 1. Crea la app Next.js con Tailwind v4 integradonpx create-next-app@latest my-app \ --typescript \ --tailwind \ --app \ --no-src-dir \ --import-alias "@/*"cd my-app# 2. Inicializa shadcn/ui - respuestas: estilo New York, base Zinc, variables CSSnpx shadcn@latest init# 3. Agrega un par de componentesnpx shadcn@latest add button card
Cuándo usarlo: Cualquier producto real. Tailwind v4 te da iteración rápida y CSS pequeño; shadcn te da primitivos accesibles y editables sin el bloqueo de una librería de componentes.
lib/utils.ts es el fusionador de nombres de clase en el que dependen los componentes de shadcn:
// lib/utils.tsimport { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));}
components.json le dice al CLI de shadcn dónde poner archivos y qué alias usar:
create-next-app --tailwind escribe en app/globals.css con @import "tailwindcss", agrega @tailwindcss/postcss a postcss.config.mjs, y agrega Tailwind v4 a package.json. No hay tailwind.config.js; Tailwind v4 lee la configuración desde CSS usando @theme, @custom-variant, y at-rules relacionadas.
npx shadcn@latest init inspecciona tu proyecto, confirma el framework (Next.js App Router), pregunta por un color base y estilo, luego escribe components.json, lib/utils.ts, y agrega variables CSS y un bloque @theme inline a app/globals.css. No instala una librería de componentes - cada npx shadcn add <nombre> obtiene el código fuente de ese componente del registro de shadcn y copia el .tsx sin procesar en components/ui/. Lo posees desde ese momento en adelante.
El helper cn() fusiona nombres de clase en dos capas: clsx maneja condicionales y arrays, luego twMerge deduplica clases Tailwind conflictivas (p.ej. px-2 gana sobre px-4 cuando ambas aparecen). Cada componente shadcn lo usa para la propiedad className para que puedas anular defaults sin guerras de especificidad.
Color base: en el momento de init, elige slate, gray, zinc (por defecto en este cookbook), neutral, o stone. Puedes cambiarlo más tarde volviendo a ejecutar npx shadcn@latest init o editando manualmente las variables CSS.
Dark mode con next-themes:npm install next-themes, envuelve <body> en un cliente ThemeProvider, y la variante de clase .dark de shadcn funciona directamente porque @custom-variant dark (&:is(.dark *)) ya está en globals.css.
Temas personalizados: edita las variables CSS :root y .dark en globals.css; no se necesita paso de reconstrucción, Tailwind v4 las detecta.
Iconos Lucide:npm install lucide-react, luego import \{ ArrowRight \} from "lucide-react". Los componentes shadcn usan Lucide internamente por lo que ya es una dependencia transitiva.
Instala un componente específico:npx shadcn@latest add dialog form input select. Las dependencias (como primitivos de Radix) se instalan automáticamente.
Monorepo: usa npx shadcn@latest init desde dentro de tu workspace de app, establece los alias de components.json para que coincidan con las rutas TS de tu workspace, y apunta tailwind.css al globals.css más cercano.
La firma de cn es cn(...inputs: ClassValue[]): string, donde ClassValue es la unión de clsx de string | number | boolean | undefined | null | ClassDictionary | ClassArray. Eso significa que puedes pasar objetos condicionales como cn("p-4", \{ "bg-primary": isActive \}) y TS no se quejará.
Los componentes personalizados tipo botón deben extender el tipo de propiedad nativa para que las refs, aria-*, y manejadores de eventos vengan gratis:
El propio Button de shadcn usa class-variance-authority (cva) y expone un tipo VariantProps<typeof buttonVariants> para que cada combinación de variante sea type-safe.
shadcn pregunta sobre TypeScript y JavaScript por separado. Incluso si tu app Next es TypeScript, el prompt de init de shadcn preguntará de nuevo. Di sí, o escribirá archivos .jsx en un proyecto TS.
La configuración de Tailwind v4 vive en CSS, no en JS. Si una publicación de blog te dice que edites tailwind.config.js, eso es Tailwind v3. En v4 el archivo no existe; usa @theme dentro de app/globals.css.
Los alias de components.json deben coincidir con las rutas de tsconfig.json. Si cambias "@/*" en tsconfig.json pero no en components.json, los componentes shadcn recién agregados importarán desde una ruta que no se resuelve.
Estrategia de clase de dark mode vs media. shadcn por defecto usa dark mode basado en clases vía @custom-variant dark (&:is(.dark *)). Si quieres prefers-color-scheme, debes editar esa línea a @custom-variant dark (&:is(@media (prefers-color-scheme: dark))) - el default hará nada silenciosamente sin una clase .dark en un antecesor.
cn() no es opcional. Si la dejas y concatenas strings de clase con literales de plantilla, las clases Tailwind conflictivas ya no se fusionarán y los overrides de props perderán aleatoriamente.
@import "tailwindcss" debe ser la primera línea de globals.css. Poner variables CSS por encima de la importación rompe el ordenamiento de capas de Tailwind v4 y tus clases text-primary aparecerán sin estilo.
postcss.config.mjs debe usar @tailwindcss/postcss, no tailwindcss. Tailwind v4 movió el plugin de PostCSS a un paquete separado; el nombre de plugin antiguo lanza un error en tiempo de compilación.
Revisión rápida de esta página - haz clic para expandir.
¿Dónde almacena Tailwind v4 su configuración?
Dentro de app/globals.css usando @theme, @custom-variant, y propiedades personalizadas de CSS. No hay tailwind.config.js en un proyecto Tailwind v4.
¿Instala `npx shadcn@latest init` una librería de componentes?
No. Instala clsx, tailwind-merge, class-variance-authority, y escribe components.json más lib/utils.ts. Los componentes se agregan uno a la vez y se copian en components/ui/ como código fuente sin procesar.
¿Qué hace `cn()` que las strings de plantilla no pueden?
Ejecuta twMerge para que los servicios de Tailwind conflictivos colapsen (p.ej. cn("p-2", "p-4") se convierte en "p-4"), lo que preserva el comportamiento de override esperado de props.
¿Puedo usar componentes shadcn dentro de Server Components?
Sí, la mayoría de ellos. Cualquier componente que use un primitivo de Radix con estado (Dialog, DropdownMenu) necesitará "use client" en el archivo del componente - shadcn ya agrega esa directiva donde sea necesaria.
¿Cómo cambio el color base después de init?
Edita las variables CSS en :root y .dark dentro de app/globals.css, o vuelve a ejecutar npx shadcn@latest init y elige un nuevo preset.
¿A qué archivo se resuelve `@/lib/utils`?
./lib/utils.ts relativo a la raíz del proyecto, porque tsconfig.json tiene "paths": \{ "@/*": ["./*"] \} y components.json lo refleja.
¿Agrupa shadcn los iconos de Lucide?
lucide-react se instala como una dependencia cuando agregas componentes que usan iconos. Puedes importar cualquier icono de Lucide directamente una vez que esté en package.json.
¿Por qué mi clase `bg-primary` no muestra color?
Generalmente porque @import "tailwindcss" no es la primera línea de globals.css, o el bloque @theme inline no hace referencia a tu variable --primary. Trampa: el orden de capas importa en Tailwind v4.
¿Por qué el dark mode no hace nada en mi página?
shadcn usa dark mode basado en clases, por lo que una clase .dark tiene que aplicarse a <html> o un antecesor. Agrega next-themes o alterna la clase tú mismo. Trampa: por defecto esto no es prefers-color-scheme.
¿Cuál es el tipo de la propiedad `className` en un Button de shadcn?
string | undefined. Nota de TS: el tipo de propiedad completo es React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>, por lo que las variantes y los atributos de botón nativos son type-safe.
¿Cómo escribo un componente que envuelve un Button de shadcn?
Usa React.ComponentPropsWithoutRef<typeof Button> (o React.ComponentProps<typeof Button>) para que tu wrapper herede variant, size, y propiedades nativas <button>. Nota de TS: prefiere ComponentPropsWithoutRef a menos que intencionalmente reenvíes refs.
¿Puedo usar esta pila sin el App Router?
Técnicamente sí - shadcn soporta Pages Router - pero este cookbook se enfoca en el App Router exclusivamente y el components.json generado asume que RSC está habilitado.