shadcn/ui en Español: Guía Completa de Instalación y Uso 2026
Aprende a instalar y usar shadcn/ui en Next.js y React: qué es realmente, componentes, temas, personalización y por qué no es una librería tradicional.
Tabla de contenidos
shadcn/ui es lo más usado en el ecosistema React en 2026 para UI, pero genera confusión porque no funciona como cualquier otra librería. Esta guía explica qué es de verdad y cómo usarlo desde cero.
Qué es shadcn/ui (y qué no es)
No es una librería npm que instalas y usas como import { Button } from 'shadcn-ui'.
Es un CLI que copia componentes bien diseñados directamente a tu proyecto. El código queda en components/ui/ y es tuyo: lo puedes modificar sin restricciones.
Los componentes usan:
- Radix UI para la lógica/accesibilidad (menús, diálogos, selects)
- Tailwind CSS para los estilos
- class-variance-authority (CVA) para variantes de componentes
Instalación en Next.js (App Router)
# Requisitos: Next.js + TypeScript + Tailwind CSS
npx create-next-app@latest mi-app --typescript --tailwind --app
cd mi-app
# Inicializar shadcn/ui
npx shadcn@latest init
El CLI preguntará:
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Would you like to use CSS variables for colors? › yes
Esto crea:
components.json— configuración de shadcn- Actualiza
tailwind.config.tscon las variables de color - Crea
lib/utils.tscon la funcióncn()
Instalación en Vite + React
npm create vite@latest mi-app -- --template react-ts
cd mi-app
npm install
npm install -D tailwindcss @tailwindcss/vite
# Configurar Tailwind en vite.config.ts
# Luego inicializar shadcn
npx shadcn@latest init
Añadir componentes
# Componentes individuales
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add dialog
npx shadcn@latest add table
npx shadcn@latest add form
# Ver todos los disponibles
npx shadcn@latest add
Cada comando copia los archivos a components/ui/.
Usar los componentes
Button
import { Button } from '@/components/ui/button';
export function EjemploButton() {
return (
<div className="flex gap-4">
<Button>Por defecto</Button>
<Button variant="destructive">Eliminar</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button size="sm">Pequeño</Button>
<Button size="lg">Grande</Button>
<Button disabled>Deshabilitado</Button>
</div>
);
}
Input + Label + Form básico
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
export function FormLogin() {
return (
<form className="space-y-4 max-w-sm">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="tu@ejemplo.com"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Contraseña</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
/>
</div>
<Button type="submit" className="w-full">
Iniciar sesión
</Button>
</form>
);
}
Dialog (modal)
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
export function ConfirmarEliminar({ onConfirmar }: { onConfirmar: () => void }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Eliminar cuenta</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>¿Estás seguro?</DialogTitle>
<DialogDescription>
Esta acción no se puede deshacer. Se eliminará tu cuenta permanentemente.
</DialogDescription>
</DialogHeader>
<div className="flex justify-end gap-3 mt-4">
<Button variant="outline">Cancelar</Button>
<Button variant="destructive" onClick={onConfirmar}>
Sí, eliminar
</Button>
</div>
</DialogContent>
</Dialog>
);
}
Tabla con datos
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
interface Usuario {
id: string;
nombre: string;
email: string;
plan: 'free' | 'pro';
}
export function TablaUsuarios({ usuarios }: { usuarios: Usuario[] }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Nombre</TableHead>
<TableHead>Email</TableHead>
<TableHead>Plan</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{usuarios.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.nombre}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
user.plan === 'pro'
? 'bg-blue-100 text-blue-800'
: 'bg-gray-100 text-gray-800'
}`}>
{user.plan.toUpperCase()}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
Personalizar estilos
Cambiar colores del tema
/* app/globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%; /* Azul personalizado */
--primary-foreground: 210 40% 98%;
--destructive: 0 84.2% 60.2%; /* Rojo */
--border: 214.3 31.8% 91.4%;
--radius: 0.5rem; /* Bordes más o menos redondeados */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--border: 217.2 32.6% 17.5%;
}
}
Modificar un componente directamente
Como el código está en tu repo, puedes editarlo:
// components/ui/button.tsx
// Añade una variante personalizada
const buttonVariants = cva(
'...clases base...',
{
variants: {
variant: {
default: '...',
destructive: '...',
// ✅ Tu variante personalizada
brand: 'bg-purple-600 text-white hover:bg-purple-700 shadow-lg',
},
},
}
);
La función cn()
Todas las variantes de shadcn usan cn(), que combina clsx y tailwind-merge. Úsala en tus propios componentes:
// lib/utils.ts (creado por shadcn init)
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Uso en tus componentes
import { cn } from '@/lib/utils';
function MiComponente({ className, activo }: { className?: string; activo: boolean }) {
return (
<div className={cn(
'p-4 rounded-lg border',
activo && 'border-blue-500 bg-blue-50',
className // permite sobreescribir desde el padre
)}>
contenido
</div>
);
}
Cuándo no usar shadcn/ui
- Si tu proyecto no usa Tailwind y no quieres introducirlo
- Si necesitas SSR/SSG sin ningún JS en cliente (los componentes Radix requieren JS)
- Si el diseño debe ser 100% personalizado sin base — en ese caso es más trabajo adaptar que construir desde cero
Para formularios complejos con shadcn, combina con React Hook Form y Zod. Para gestión de datos del servidor, TanStack Query v5 es el complemento natural si usas shadcn en tu UI.