Configuración de shadcn/ui
Instala, configura y aplica tema a shadcn/ui — componentes copy-paste construidos sobre Radix UI y Tailwind CSS.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
# Inicializa shadcn en tu proyecto
npx shadcn@latest init
# Agrega componentes específicos
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add card
# Agrega varios a la vez
npx shadcn@latest add button input card dialog form// components.json — generado por init
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}// lib/utils.ts — la utilidad cn
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Cuándo usarlo: Al inicio de cualquier proyecto Next.js o React en el que quieras componentes de UI pulidos y accesibles con control total sobre el código fuente.
Ejemplo funcional
/* app/globals.css — tema shadcn con variables CSS */
@import "tailwindcss";
@theme inline {
--color-background: var(--background);
--color-foreground: var(--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);
}
@layer base {
:root {
--background: oklch(1 0 0);
--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);
--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);
}
}// app/layout.tsx
import { ThemeProvider } from "next-themes";
import "@/app/globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className="bg-background text-foreground antialiased">
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}// Demostración rápida con componentes shadcn
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function LoginCard() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Iniciar sesión</CardTitle>
<CardDescription>Introduce tus credenciales para continuar.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Correo electrónico</Label>
<Input id="email" type="email" placeholder="you@example.com" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Contraseña</Label>
<Input id="password" type="password" />
</div>
<Button className="w-full">Iniciar sesión</Button>
</CardContent>
</Card>
);
}Qué demuestra esto:
- Inicialización de shadcn e instalación de componentes
- Theming basado en variables CSS con modo claro y oscuro
- Configuración de
ThemeProviderdesdenext-themes - Composición de componentes (Card + Input + Button + Label)
- La utilidad
cn()para fusionar nombres de clase
Profundización
Cómo funciona
- shadcn/ui NO es un paquete — copia el código fuente de los componentes en tu proyecto
- Los componentes viven en
components/ui/y son totalmente editables - Cada componente usa primitivos de Radix UI para accesibilidad y comportamiento
- El estilo usa utilidades de Tailwind CSS + variables CSS para el theming
- La utilidad
cn()fusiona nombres de clase conclsxy resuelve conflictos de Tailwind contailwind-merge - Las variables CSS permiten cambiar el tema en tiempo de ejecución sin recompilar
Variaciones
Colores de tema personalizados:
/* Usa el generador de temas de shadcn en ui.shadcn.com/themes */
:root {
--primary: oklch(0.5 0.2 260); /* azul personalizado */
--destructive: oklch(0.5 0.2 25); /* rojo personalizado */
}Agregar un componente con dependencias:
# Algunos componentes instalan automáticamente sus dependencias
npx shadcn@latest add dialog
# Esto instala @radix-ui/react-dialog y el componente DialogConfiguración de alias de rutas:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}Notas de TypeScript
// La utilidad cn está completamente tipada
import { cn } from "@/lib/utils";
cn("bg-red-500", undefined, false, "text-white");
// devuelve "bg-red-500 text-white"
// Los props del componente extienden tipos de Radix
// p. ej., Button extiende React.ButtonHTMLAttributes<HTMLButtonElement>
import { Button, type ButtonProps } from "@/components/ui/button";
function MyButton(props: ButtonProps) {
return <Button variant="outline" {...props} />;
}Errores comunes
-
No es un paquete — es código fuente —
npm install shadcnes incorrecto. Solución: Usanpx shadcn@latest add <component>para copiar componentes en tu proyecto. -
Compatibilidad con Tailwind v4 — shadcn/ui es compatible con Tailwind v4. Asegúrate de ejecutar la última versión de
npx shadcn@latest init, que genera el CSS compatible con v4. -
Las variables CSS deben estar definidas — Si omites la configuración de variables CSS, los componentes se renderizan sin colores. Solución: Asegúrate de que tu
globals.cssincluya los bloques completos de variables:rooty.dark. -
Ruta de importación de
cn()— Los componentes importan desde@/lib/utils. Si tu alias es distinto, actualizacomponents.jsony ejecuta init de nuevo. -
Actualizaciones de componentes — Como los componentes se copian, no se actualizan automáticamente. Solución: Vuelve a ejecutar
npx shadcn@latest add <component>para obtener la última versión (pero esto sobrescribe tus cambios).
Alternativas
| Alternativa | Úsala cuando | No la uses cuando |
|---|---|---|
| Radix UI (bare) | Quieres primitivos sin opiniones sobre el estilo | Quieres componentes preestilizados y listos para usar |
| Headless UI | Usas Tailwind pero prefieres otra biblioteca de primitivos | Quieres el ecosistema completo de shadcn |
| MUI / Chakra UI | Quieres una biblioteca de componentes totalmente empaquetada | Quieres control total sobre el código fuente de los componentes |
| Mantine | Quieres una biblioteca de componentes React con todo incluido | Usas Tailwind para el estilo |
Preguntas frecuentes
¿Es shadcn/ui un paquete npm que instalas?
No. shadcn/ui copia el código fuente de los componentes en tu proyecto. Usa npx shadcn@latest add <component>, no npm install shadcn.
¿Qué hace npx shadcn@latest init?
- Genera
components.jsoncon la configuración del proyecto - Crea
lib/utils.tscon la utilidadcn() - Configura variables CSS en
globals.csspara el theming
¿Qué es la utilidad cn() y por qué se necesita?
cn() combina clsx (fusión condicional de clases) con tailwind-merge (resolución de conflictos de Tailwind). Garantiza que las sobrescrituras de className funcionen correctamente en los componentes shadcn.
¿Dónde viven los componentes shadcn en el proyecto?
En components/ui/ por defecto. Cada componente es un archivo fuente totalmente editable, no una dependencia de node_modules.
¿Cómo personalizas los colores del tema shadcn?
Edita las variables CSS en globals.css bajo :root (claro) y .dark (oscuro). Usa el generador de temas en ui.shadcn.com/themes para presets rápidos.
¿Cómo funciona el modo oscuro con shadcn?
Las variables CSS se definen dos veces: una en :root para el modo claro y otra en .dark para el modo oscuro. Usa next-themes con attribute="class" para alternar la clase .dark.
¿Cómo agregas varios componentes a la vez?
npx shadcn@latest add button input card dialog formTrampa: Los componentes se renderizan sin colores tras la configuración. ¿Qué está mal?
Faltan las variables CSS. Asegúrate de que globals.css incluya los bloques completos de variables :root y .dark generados por npx shadcn@latest init.
Trampa: Actualizaste un componente pero perdiste tus cambios. ¿Qué pasó?
Volver a ejecutar npx shadcn@latest add <component> sobrescribe el archivo. Haz una copia de seguridad de tus personalizaciones antes de actualizar, o usa control de versiones.
¿Cómo se tipa cn() en TypeScript?
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}ClassValue acepta strings, arrays, objetos, undefined, null y booleanos.
¿Cómo usas los tipos de componentes shadcn en TypeScript?
import { Button, type ButtonProps } from "@/components/ui/button";
function MyButton(props: ButtonProps) {
return <Button variant="outline" {...props} />;
}Los props del componente extienden sus tipos subyacentes de Radix o del elemento HTML.
Relacionado
- Button — variantes y uso del botón
- Dialog — diálogos modales
- Form — componentes de formulario con RHF + Zod
- Tailwind Setup — configuración de Tailwind v4