Qué es Shadcn/UI? Tutorial para Principiantes

Audrey Lopez

Audrey Lopez

14 June 2025

Qué es Shadcn/UI? Tutorial para Principiantes

Para los desarrolladores web, la búsqueda del conjunto de herramientas de interfaz de usuario (UI) perfecto es un esfuerzo constante. Durante años, los desarrolladores de React han dependido de bibliotecas de componentes tradicionales como Material-UI (MUI), Ant Design y Chakra UI. Estas bibliotecas ofrecen una gran cantidad de componentes preconstruidos, prometiendo acelerar el desarrollo. Sin embargo, a menudo vienen con una contrapartida: falta de control, anulaciones de estilo que se sienten como una batalla y tamaños de paquete inflados.

Presentamos Shadcn UI, un enfoque que cambia el paradigma y que ha arrasado en la comunidad de React. No es una biblioteca de componentes como las que estás acostumbrado; es algo mejor. Es una colección de componentes bellamente diseñados, accesibles y infinitamente reutilizables que no instalas desde npm como una dependencia, sino que los copias directamente en tu proyecto.

Este tutorial completo de 4000 palabras servirá como tu guía definitiva, llevándote de ser un principiante total a un practicante seguro de Shadcn UI. Exploraremos su filosofía fundamental, recorreremos una configuración detallada, construiremos interfaces de usuario complejas, dominaremos el theming avanzado y el manejo de formularios, y discutiremos las mejores prácticas para aplicaciones a gran escala. Prepárate para replantearte lo que esperas de un conjunto de herramientas de UI.

💡
¿Quieres una excelente herramienta de prueba de API que genere hermosa documentación de API?

¿Quieres una plataforma integrada y todo en uno para que tu equipo de desarrolladores trabaje junto con la máxima productividad?

¡Apidog cumple todas tus demandas y reemplaza a Postman a un precio mucho más asequible!
botón

La Filosofía de Shadcn UI - Una Nueva Forma de Construir

Antes de escribir una sola línea de código, es fundamental comprender por qué existe Shadcn UI y qué problemas resuelve. Comprender esta filosofía central es la clave para desbloquear todo su potencial.

Lo Que Shadcn UI No Es

Lo Que Shadcn UI Es

La principal ventaja de este modelo es la fusión de velocidad y control. Obtienes la velocidad inicial de usar componentes preconstruidos sin sacrificar la flexibilidad y mantenibilidad a largo plazo que proviene de ser dueño de tu propio código.


Preparando el Escenario - Configuración e Instalación del Proyecto

Pasemos de la teoría a la práctica. Configuraremos un nuevo proyecto desde cero. Para esta guía, utilizaremos principalmente Next.js, ya que sus componentes de servidor y su enrutamiento basado en archivos se alinean perfectamente con el espíritu de Shadcn UI. También cubriremos brevemente la configuración para Vite.

Paso 1: Prerrequisitos del Entorno

Asegúrate de que tu entorno de desarrollo esté listo. Necesitarás:

Paso 2: Creando una Nueva Aplicación Next.js

Abre tu terminal y ejecuta el siguiente comando para iniciar un nuevo proyecto Next.js.Bash

npx create-next-app@latest my-pro-shadcn-app --typescript --tailwind --eslint

Este comando crea una nueva aplicación en un directorio llamado my-pro-shadcn-app. Hemos incluido algunas flags importantes:

El instalador te hará algunas preguntas. Estas son las opciones recomendadas para una configuración moderna de Next.js 14+:

✔ Would you like to use `src/` directory? … No / **Yes**
✔ Would you like to use App Router? (recommended) … No / **Yes**
✔ Would you like to customize the default import alias? … **No** / Yes

Usar el App Router es una práctica estándar, y el directorio src/ ayuda a organizar el código. Una vez hecho, navega a tu nuevo proyecto:Bash

cd my-pro-shadcn-app

Paso 3: El Comando init - Dando Vida a Shadcn UI

Este es el paso más importante. Shadcn UI proporciona una herramienta CLI para configurar tu proyecto. Ejecuta el siguiente comando desde el directorio raíz de tu proyecto:Bash

npx shadcn-ui@latest init

Esto activará un cuestionario interactivo para configurar tu proyecto. Analicemos cada pregunta y su significado:

Después de confirmar, la CLI hace su magia:

  1. Instala Dependencias: Añade paquetes necesarios como tailwindcss-animate y class-variance-authority.
  2. Crea components.json: Almacena tus opciones de configuración.
  3. Actualiza tailwind.config.ts: Inyecta el plugin de Shadcn UI y la configuración de theming.
  4. Actualiza globals.css: Añade un gran bloque de variables CSS que definen toda tu paleta de colores, radios de borde y más.
  5. Crea lib/utils.ts: Este archivo exporta una función de ayuda cn, que es una utilidad inteligente para fusionar condicionalmente clases de Tailwind CSS.

Tu proyecto ahora está completamente configurado.

(Alternativa: Configuración con Vite)

Si estás usando Vite con React, el proceso es muy similar. Después de configurar un proyecto Vite + React + TS, instalarías manualmente Tailwind CSS y luego ejecutarías npx shadcn-ui@latest init. La CLI es lo suficientemente inteligente como para detectar una configuración de Vite y hará preguntas ligeramente diferentes sobre las ubicaciones de los archivos (por ejemplo, index.css en lugar de globals.css).


Construyendo una UI - De Componentes Simples a Diseños Complejos

Con la configuración completa, comencemos a construir. El flujo de trabajo principal es: identificar una necesidad, añadir el componente, usarlo.

Paso 4: Añadiendo y Usando Tus Primeros Componentes

Limpiemos el código boilerplate predeterminado de Next.js y construyamos una interfaz simple.

1. Añadir un Botón:Bash

npx shadcn-ui@latest add button

Observa lo que sucede: se crea un nuevo archivo, src/components/ui/button.tsx. Este es tu botón. Tú eres el dueño.

2. Añadir una Tarjeta (Card):Bash

npx shadcn-ui@latest add card

Este comando es más interesante. Crea src/components/ui/card.tsx. Si inspeccionas este archivo, verás que exporta múltiples componentes: Card, CardHeader, CardTitle, CardDescription, CardContent y CardFooter. Este es un patrón común para componentes compuestos.

3. Construir la UI:

Ahora, abre src/app/page.tsx y reemplaza su contenido con lo siguiente:TypeScript

import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input"; // Añadiremos esto después
import { Label } from "@/components/ui/label";   // Y esto

export default function Home() {
  return (
    <main className="flex min-h-screen items-center justify-center bg-background p-8">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle className="text-2xl">Crear Proyecto</CardTitle>
          <CardDescription>
            Despliega tu nuevo proyecto en un clic.
          </CardDescription>
        </CardHeader>
        <CardContent className="grid gap-4">
          <div className="grid gap-2">
            <Label htmlFor="name">Nombre</Label>
            <Input id="name" placeholder="Nombre de tu proyecto" />
          </div>
          <div className="grid gap-2">
            <Label htmlFor="framework">Framework</Label>
            {/* Reemplazaremos esto con un componente Select más tarde */}
            <Input id="framework" placeholder="ej. Next.js" />
          </div>
        </CardContent>
        <CardFooter>
          <Button className="w-full">Desplegar</Button>
        </CardFooter>
      </Card>
    </main>
  );
}

Nuestro código aún no se ejecutará porque nos faltan los componentes Input y Label. Añadámoslos:Bash

npx shadcn-ui@latest add input
npx shadcn-ui@latest add label

Ahora, ejecuta tu servidor de desarrollo:Bash

npm run dev

Navega a http://localhost:3000. Verás un formulario limpio y de aspecto profesional dentro de una tarjeta. Observa cómo usamos clases de utilidad como w-full, max-w-md y grid directamente en nuestro JSX para controlar el diseño. Este es el poder de combinar Shadcn y Tailwind CSS.

Paso 5: Introduciendo Componentes Más Sofisticados

Las entradas estáticas son buenas, pero las aplicaciones reales necesitan elementos interactivos. Mejoremos nuestro formulario.

1. Añadir un Componente Select: La entrada "Framework" debería ser un desplegable. Añadamos el componente Select. Este es más complejo y tiene dependencias de otros componentes.Bash

npx shadcn-ui@latest add select

La CLI es inteligente. Verá que Select requiere un componente Popover para funcionar y te pedirá permiso para instalarlo a él y a sus dependencias también. Esta es una característica fantástica que te evita tener que rastrear dependencias manualmente.

2. Integrar el Select Componente: Reemplaza la entrada Input para "Framework" en src/app/page.tsx con el nuevo componente Select.TypeScript

// Añade estas importaciones al principio
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

// ... dentro de CardContent
<div className="grid gap-2">
  <Label htmlFor="framework">Framework</Label>
  <Select>
    <SelectTrigger id="framework">
      <SelectValue placeholder="Selecciona un framework" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="nextjs">Next.js</SelectItem>
      <SelectItem value="sveltekit">SvelteKit</SelectItem>
      <SelectItem value="astro">Astro</SelectItem>
      <SelectItem value="nuxt">Nuxt.js</SelectItem>
    </SelectContent>
  </Select>
</div>

Actualiza tu navegador. Ahora tienes un desplegable de selección completamente funcional y accesible, completo con animaciones y navegación de teclado adecuada, todo gracias a Radix UI trabajando en segundo plano.

3. Añadiendo Feedback al Usuario con Toast: ¿Qué sucede cuando un usuario hace clic en "Deploy"? Deberíamos darle algo de feedback. El componente Toast es perfecto para esto.

Primero, añádelo:Bash

npx shadcn-ui@latest add toast

A continuación, para usar toasts, necesitas añadir un componente <Toaster /> a tu layout raíz para que pueda mostrarse en cualquier parte de la aplicación. Abre src/app/layout.tsx y modifícalo:TypeScript

import { Toaster } from "@/components/ui/toaster" // Importa el Toaster

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Toaster /> {/* Añádelo aquí, justo antes de cerrar body */}
      </body>
    </html>
  )
}

Ahora, necesitamos una forma de activar el toast. Usaremos el hook useToast. Actualicemos src/app/page.tsx para convertirlo en un componente de cliente y manejar el clic del botón.TypeScript

'use client'; // <-- Añade esto al principio del archivo

// ... otras importaciones
import { useToast } from "@/components/ui/use-toast";

export default function Home() {
  const { toast } = useToast(); // Obtén la función toast del hook

  function handleDeploy() {
    toast({
      title: "Despliegue Programado!",
      description: "Tu proyecto 'Nombre de tu proyecto' se está desplegando.",
      duration: 5000,
    });
  }

  return (
    <main className="flex min-h-screen items-center justify-center bg-background p-8">
      <Card className="w-full max-w-md">
        {/* ... CardHeader y CardContent ... */}
        <CardFooter>
          <Button className="w-full" onClick={handleDeploy}> {/* Añade el manejador onClick */}
            Desplegar
          </Button>
        </CardFooter>
      </Card>
    </main>
  );
}

Ahora, cuando hagas clic en el botón "Desplegar", aparecerá una elegante notificación en la esquina de tu pantalla.


Construyendo un Formulario Profesional con Validación

La mayoría de las aplicaciones del mundo real requieren un manejo robusto de formularios, incluida la validación del lado del cliente. La forma oficial de manejar esto con Shadcn UI es combinándolo con react-hook-form para la gestión del estado y zod para la validación del esquema. Construyámoslo.

Paso 6: Instalando Dependencias del Formulario

Primero, instalemos las bibliotecas necesarias:Bash

npm install react-hook-form zod @hookform/resolvers

Paso 7: Añadiendo el Componente Form de Shadcn

Shadcn UI proporciona un componente Form especial que actúa como un wrapper para conectar sin problemas react-hook-form con tus componentes de UI.Bash

npx shadcn-ui@latest add form

Esto añadirá src/components/ui/form.tsx. Este archivo proporciona un conjunto de componentes conscientes del contexto (Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage) que reducen drásticamente el código boilerplate.

Paso 8: Creando el Esquema de Validación

En tu src/app/page.tsx, definamos la forma y las reglas de los datos de nuestro formulario usando zod.TypeScript

// Añade estas importaciones al principio
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

Ahora, creemos el esquema justo encima de nuestro componente Home:TypeScript

const formSchema = z.object({
  projectName: z.string().min(2, {
    message: "El nombre del proyecto debe tener al menos 2 caracteres.",
  }).max(50, {
    message: "El nombre del proyecto no debe exceder los 50 caracteres.",
  }),
  framework: z.string({
    required_error: "Por favor, selecciona un framework para mostrar.",
  }),
});

Este esquema define dos campos: projectName debe ser una cadena de texto entre 2 y 50 caracteres, y framework es una cadena de texto obligatoria.

Paso 9: Conectando el Formulario

Ahora, refactoricemos nuestro componente Home para usar todas estas nuevas herramientas.TypeScript

export default function Home() {
  const { toast } = useToast();

  // 1. Define tu formulario.
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      projectName: "",
    },
  });

  // 2. Define un manejador de envío.
  function onSubmit(values: z.infer<typeof formSchema>) {
    // Haz algo con los valores del formulario.
    // ✅ Esto será type-safe y validado.
    console.log(values);
    toast({
      title: "Has enviado los siguientes valores:",
      description: (
        <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
          <code className="text-white">{JSON.stringify(values, null, 2)}</code>
        </pre>
      ),
    });
  }

  // 3. Construye el JSX con los componentes Form de Shadcn
  return (
    <main className="flex min-h-screen items-center justify-center bg-background p-8">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle className="text-2xl">Crear Proyecto</CardTitle>
          <CardDescription>
            Despliega tu nuevo proyecto en un clic.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <Form {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
              <FormField
                control={form.control}
                name="projectName"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Nombre</FormLabel>
                    <FormControl>
                      <Input placeholder="Nombre de tu proyecto" {...field} />
                    </FormControl>
                    <FormDescription>
                      Este es tu nombre público de visualización.
                    </FormDescription>
                    <FormMessage /> {/* Muestra errores de validación */}
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name="framework"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Framework</FormLabel>
                    <Select onValueChange={field.onChange} defaultValue={field.value}>
                      <FormControl>
                        <SelectTrigger>
                          <SelectValue placeholder="Selecciona un framework" />
                        </SelectTrigger>
                      </FormControl>
                      <SelectContent>
                        <SelectItem value="nextjs">Next.js</SelectItem>
                        <SelectItem value="sveltekit">SvelteKit</SelectItem>
                        <SelectItem value="astro">Astro</SelectItem>
                        <SelectItem value="nuxt">Nuxt.js</SelectItem>
                      </SelectContent>
                    </Select>
                    <FormDescription>
                      El framework que quieres desplegar.
                    </FormDescription>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <Button type="submit" className="w-full">Desplegar</Button>
            </form>
          </Form>
        </CardContent>
      </Card>
    </main>
  );
}

Este es un fragmento de código considerable, pero es un patrón increíblemente potente y escalable. El componente FormField maneja todas las conexiones de estado, y FormMessage muestra automáticamente el error de validación correcto de tu esquema zod cuando un usuario interactúa con el campo. Intenta enviar el formulario con un nombre de proyecto vacío para ver la validación en acción.


Dominando el Theming y la Personalización

El verdadero poder de Shadcn UI se desata cuando empiezas a hacerlo tuyo.

Paso 10: Theming Avanzado con Variables CSS

Todo tu tema está definido por variables CSS en src/app/globals.css. Abre este archivo y busca los bloques :root y .dark.CSS

/* Ejemplo de globals.css */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  /* ... y muchos más */
  --radius: 0.5rem;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --primary: 210 40% 98%;
  --primary-foreground: 222.2 47.4% 11.2%;
  /* ... */
}

Implementando el Modo Oscuro:

Shadcn está preconfigurado para el modo oscuro gracias al bloque de clase .dark y la estrategia darkMode: "class" de Tailwind en tailwind.config.ts. Todo lo que necesitas es una forma de alternar la clase dark en el elemento <html>. Una biblioteca popular para esto es next-themes.

  1. Instálala: npm install next-themes
  2. Crea un componente ThemeProvider (src/components/theme-provider.tsx): TypeScript
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
  1. Envuelve tu RootLayout en este proveedor (src/app/layout.tsx): TypeScript
import { ThemeProvider } from "@/components/theme-provider"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
          <Toaster />
        </ThemeProvider>
      </body>
    </html>
  )
}
  1. Finalmente, crea un botón de alternancia (por ejemplo, src/components/mode-toggle.tsx): TypeScript
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ModeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <Button
      variant="outline"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Alternar tema</span>
    </Button>
  )
}

Ahora puedes colocar este <ModeToggle /> en cualquier parte de tu aplicación para obtener un alternador de modo oscuro consciente del sistema y anulable por el usuario.

Paso 11: Personalizando el Código Fuente del Componente

Este es el superpoder definitivo. Supongamos que quieres una nueva variante de éxito para tu botón que tenga un fondo verde.

Abre src/components/ui/button.tsx. Encuentra la definición de buttonVariants. Usa cva (Class Variance Authority). Simplemente añade una nueva variante:TypeScript

const buttonVariants = cva(
  // ... estilos base
  {
    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",
        success: "bg-green-600 text-white hover:bg-green-600/90", // Nuestra nueva variante
      },
      // ... variantes de tamaño
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

Eso es todo. Ahora puedes usarlo en tu código: <Button variant="success">Success</Button>. No necesitaste escribir anulaciones CSS complejas. Simplemente editaste el propio código fuente del componente. Este flujo de trabajo es simple, predecible e increíblemente potente.


Parte 6: Mejores Prácticas y el Camino a Seguir

A medida que tu aplicación crece, aquí tienes algunas mejores prácticas a tener en cuenta.

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs