Cambiar tema

Guía completa de instalación y personalización de temas en shadcn/ui (con variables CSS)

Easton editorial illustration: design-system assembly tray

La primera vez que usé shadcn/ui, me desconcertó lo de «no es un paquete npm». ¿Copiar código al proyecto? Suena demasiado primitivo.

Después de usarlo unas cuantas veces entendí por qué es tan potente: tienes el código fuente de todos los componentes, lo modificas como quieras, sin conflictos de versión ni limitaciones del diseño impuesto por la biblioteca.

Hoy hablamos de la instalación, la configuración y la personalización de temas en shadcn/ui, con foco en cómo usar variables CSS para un sistema de diseño con identidad de marca. Al terminar este artículo deberías poder configurar lo básico en unos 5 minutos y dedicar una hora más a ajustar el tema a tu gusto.


1. Instalación rápida: dos enfoques

Enfoque 1: inicialización con CLI (recomendado)

En un proyecto nuevo, ejecuta directamente:

npx shadcn@latest init

Te hará varias preguntas: ¿TypeScript o JavaScript? ¿Qué estilo? ¿Tema por defecto? Todo es interactivo; sigue las indicaciones.

Tras la instalación, tu proyecto tendrá archivos nuevos:

  • components.json — archivo de configuración
  • lib/utils.ts — funciones de utilidad
  • components/ui/ — directorio de componentes

Añadir componentes también es sencillo. Por ejemplo, un botón:

npx shadcn@latest add button

El código se copia automáticamente a components/ui/button.tsx; solo tienes que importarlo y usarlo.

Aquí hay una trampa: si el proyecto ya lleva tiempo, tailwind.config.js y globals.css pueden tener mucha configuración propia. El comando init de shadcn sobrescribe esos archivos, así que conviene instalarlo al principio.

Un blogger lo resumió bien: trata shadcn/ui como una de las «primeras dependencias» del proyecto; no lo dejes para más tarde. Aprendí la lección a las duras.

Enfoque 2: instalación manual (proyectos existentes)

Si el proyecto ya está maduro y el riesgo de que la CLI sobrescriba la config es demasiado alto, instala manualmente.

Pasos:

Paso 1: asegúrate de tener Tailwind CSS

Los componentes de shadcn están escritos con Tailwind. Si aún no lo tienes, instálalo primero; la documentación oficial es clara.

Paso 2: instala dependencias

npm install class-variance-authority clsx tailwind-merge
npm install lucide-react

class-variance-authority (CVA) es muy útil; lo usarás al definir variantes de componentes.

Paso 3: configura alias de rutas

En tsconfig.json, añade:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"]
    }
  }
}

Así puedes importar con @/components/ui/button en lugar de escribir ../../../.

Paso 4: crea components.json

En la raíz del proyecto:

{
  "style": "new-york",
  "rsc": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

La línea cssVariables: true es clave: indica que usaremos variables CSS para el tema, no utility classes de Tailwind.

Paso 5: añade estilos

Incluye los estilos base de shadcn en globals.css; más adelante profundizamos en el tema.


2. Entender el sistema de temas: cómo funcionan las variables CSS

El sistema de temas de shadcn/ui se basa en una convención simple: cada color tiene variables background y foreground.

¿Qué significa? Un ejemplo:

:root {
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
}

--primary es el fondo del botón; --primary-foreground, el color del texto. Al emparejarlos, cambias una variable y todos los componentes relacionados se actualizan.

Lista de variables CSS

shadcn/ui define por defecto estas variables:

VariableUso
--backgroundFondo de página
--foregroundTexto de página
--cardFondo de tarjeta
--card-foregroundTexto de tarjeta
--popoverFondo de capa emergente
--popover-foregroundTexto de capa emergente
--primaryColor principal (botones, enlaces)
--primary-foregroundTexto sobre color principal
--secondaryColor secundario
--secondary-foregroundTexto sobre color secundario
--mutedFondo suave
--muted-foregroundTexto suave
--accentColor de acento
--accent-foregroundTexto sobre acento
--destructiveAcciones peligrosas (botón eliminar)
--destructive-foregroundTexto sobre acción destructiva
--borderBordes
--inputCampos de entrada
--ringAnillo de foco

Parecen muchas, pero si captas el patrón background/foreground, se memorizan con facilidad.

El secreto del formato HSL

Quizá notes que los valores de color de shadcn no usan HSL estándar:

/* ❌ HSL estándar */
--primary: hsl(222.2, 47.4%, 11.2%);

/* ✅ Formato shadcn */
--primary: 222.2 47.4% 11.2%;

¿Por qué en formato «desnudo»?

Porque Tailwind admite modificadores de opacidad, como bg-primary/50 para el 50% de transparencia. Con hsl() completo, esa función no funciona.

Con el formato desnudo, Tailwind añade hsl() y la opacidad por ti. Diseño inteligente.


3. Personalizar tu tema de marca

Método 1: modificar variables CSS directamente

Lo más simple: abre globals.css, localiza :root y cambia los valores de color.

Por ejemplo, pasar el primario del azul por defecto al violeta:

:root {
  --primary: 270 60% 60%;
  --primary-foreground: 0 0% 100%;
}

.dark {
  --primary: 270 60% 70%;
  --primary-foreground: 0 0% 0%;
}

Guarda y todos los botones y enlaces con bg-primary pasarán a violeta.

Método 2: espacio de color OKLCH (Tailwind v4)

Si usas Tailwind v4, considera OKLCH. Frente a HSL, la percepción del color se acerca más al ojo humano y las escalas resultan más uniformes.

:root {
  --primary: oklch(0.6 0.2 270);
  --primary-foreground: oklch(0.98 0 0);
}

Los tres parámetros de oklch(0.6 0.2 270) son:

  • 0.6 — luminosidad (0-1)
  • 0.2 — croma (aprox. 0-0.4)
  • 270 — ángulo de matiz (0-360)

Método 3: herramientas online

¿Te resulta tedioso combinar colores? Usa herramientas online.

Recomiendo: Shadcn Theme Generator

Elige un color primario y la herramienta genera el conjunto completo de variables CSS, en claro y oscuro. Copia y pega en globals.css.


4. Configuración del modo oscuro

Cambio de tema con next-themes

shadcn/ui no trae cambio de tema integrado, pero puedes usar next-themes.

Instálalo:

npm install next-themes

Configura en layout.tsx:

import { ThemeProvider } from "next-themes"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="es" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

Puntos clave:

  • suppressHydrationWarning es obligatorio; si no, verás avisos de hidratación
  • attribute="class" indica que el tema se cambia con clases
  • defaultTheme="system" sigue el sistema por defecto
  • enableSystem activa la detección del tema del sistema

Botón para cambiar de tema

Con el hook useTheme obtienes el tema actual y la función de cambio:

import { useTheme } from "next-themes"
import { Moon, Sun } from "lucide-react"

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

  return (
    <button
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
      className="p-2 rounded-md hover:bg-accent"
    >
      {theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
    </button>
  )
}

Modo oscuro por defecto

Si quieres que el sitio arranque en oscuro, hay dos formas:

Opción 1: clase dark fija

<html lang="es" className="dark">

El tema queda fijado en oscuro; no hay cambio.

Opción 2: tema por defecto

<ThemeProvider
  attribute="class"
  defaultTheme="dark"  // Oscuro por defecto
  enableSystem={false} // Desactiva detección del sistema
>

El usuario puede cambiar manualmente, pero el estado inicial es oscuro.


5. Personalización avanzada: variantes de componentes

Variantes personalizadas con CVA

A veces necesitas varios estilos de botón: «peligro», «éxito», «degradado». CVA facilita definir esas variantes.

import { cva, type VariantProps } from "class-variance-authority"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
  {
    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",
      },
      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",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

Uso en componentes:

<button className={buttonVariants({ variant: "destructive", size: "lg" })}>
  Eliminar
</button>

No modifiques directamente el código fuente de shadcn

Es una cuestión de mejores prácticas.

El código de shadcn está en tu proyecto y puedes tocarlo, pero conviene no alterar los archivos originales: crea componentes wrapper.

¿Por qué? shadcn actualiza componentes con frecuencia. Si cambias los originales, al actualizar tendrás que hacer merge manual. Es un dolor de cabeza.

Enfoque mejor:

// components/brand-button.tsx
import { Button } from "@/components/ui/button"
import { cva } from "class-variance-authority"

const brandButtonVariants = cva("...", {
  variants: {
    brand: {
      primary: "bg-brand-primary text-white",
      secondary: "bg-brand-secondary text-black",
    },
  },
})

export function BrandButton({ brand, ...props }) {
  return <Button className={brandButtonVariants({ brand })} {...props} />
}

El Button original queda intacto; tienes tu BrandButton y las actualizaciones de shadcn no rompen tu personalización.


6. Problemas frecuentes y trampas

Problema 1: los estilos no se aplican tras instalar

Revisa:

  1. ¿Está importado globals.css en layout.tsx?
  2. ¿La config content de Tailwind incluye components/**/*?
  3. ¿Las rutas en components.json son correctas?

Problema 2: parpadeo al cambiar de tema

Suele ser desajuste de hidratación. Asegúrate de:

  1. suppressHydrationWarning en la etiqueta <html>
  2. ThemeProvider envolviendo toda la app
  3. No leer theme en renderizado servidor (será undefined)

Problema 3: las variables CSS no funcionan

Posibles causas:

  1. Nombre mal escrito (--primary-foreground, no --primaryForeground)
  2. Falta el bloque .dark correspondiente
  3. Formato incorrecto (usa HSL desnudo u OKLCH)

Problema 4: conflictos de estilos entre componentes

Si el proyecto ya tiene un sistema de estilos, puede chocar con shadcn. Soluciones:

  1. Namespace en componentes shadcn (p. ej. shadcn-button)
  2. Ajustar prioridades de capas en Tailwind
  3. Variantes propias con CVA, sin depender del estilo por defecto

7. Resumen

Instalar y configurar shadcn/ui es sencillo; lo importante es entender la filosofía de «copiar código, no instalar paquete». Ganas control total; pagas manteniendo el código en cada proyecto.

En temas, el sistema de variables CSS es elegante: cambias unos valores y toda la app se recolorea. Con next-themes, claro/oscuro son unas pocas líneas.

Recomendaciones finales:

  1. En proyectos nuevos, usa la CLI y evita configuración manual
  2. Variables semánticas (primary, secondary), no nombres de color concretos
  3. Prueba contraste en claro y oscuro para legibilidad
  4. Componentes wrapper, no parches al fuente — facilita actualizaciones

La próxima vez que necesites una UI con tema rápido, prueba shadcn/ui. La satisfacción del copy-paste se entiende usándolo.



Referencias

Instalación y personalización de temas en shadcn/ui

Instala shadcn/ui desde cero, configura el sistema de temas y construye un diseño con identidad de marca

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Inicialización rápida con CLI

    En un proyecto nuevo, ejecuta el comando de instalación:

    • npx shadcn@latest init
    • Elige TypeScript/estilo New York/tema por defecto
    • Espera a que la CLI termine la configuración
  2. 2

    Step 2: Modificar el color primario de marca

    Edita las variables CSS en globals.css:

    • Abre app/globals.css
    • Localiza la variable --primary bajo :root
    • Cámbiala por tu color de marca (formato HSL u OKLCH)
    • Ajusta también --primary-foreground para mantener el contraste
  3. 3

    Step 3: Configurar el modo oscuro

    Instala y configura next-themes:

    • npm install next-themes
    • Añade ThemeProvider en layout.tsx
    • Usa suppressHydrationWarning para evitar avisos de hidratación
    • Crea un componente para cambiar de tema
  4. 4

    Step 4: Crear variantes de componentes

    Usa CVA para definir estilos personalizados:

    • Instala class-variance-authority
    • Define variants y defaultVariants
    • Aplica buttonVariants() en los componentes
    • Mantén intactos los componentes originales de shadcn

FAQ

¿En qué se diferencia shadcn/ui de las bibliotecas UI tradicionales?
shadcn/ui no es un paquete npm: copia el código fuente de los componentes en tu proyecto. La ventaja es control total y personalización sin conflictos de versión; la desventaja es mantener el código de los componentes en cada proyecto.
¿Por qué conviene instalar shadcn/ui al inicializar un proyecto nuevo?
Porque el comando init de shadcn sobrescribe tailwind.config.js y globals.css. Si el proyecto ya lleva tiempo en desarrollo, la configuración de esos archivos se perderá; cuanto antes instales, mejor.
¿Por qué las variables CSS usan formato «desnudo» en lugar de HSL estándar?
El formato desnudo (como 222.2 47.4% 11.2%) permite los modificadores de opacidad de Tailwind, por ejemplo bg-primary/50 para 50% de transparencia. Con hsl() completo, esa función no funciona.
¿Cómo cambio el color primario de marca?
Abre globals.css, localiza --primary y --primary-foreground bajo :root y cámbialos por los valores de tu marca. Al guardar, todos los componentes que usen bg-primary se actualizarán automáticamente.
¿Por qué parpadea el modo oscuro?
Suele deberse a un desajuste de hidratación. Asegúrate de que la etiqueta html tenga suppressHydrationWarning, que ThemeProvider envuelva toda la app y de no leer theme durante el renderizado en servidor.
¿Debo modificar directamente el código fuente de los componentes shadcn?
No es recomendable. shadcn actualiza los componentes con frecuencia; si modificas los archivos originales, tendrás que hacer merge manual al actualizar. Mejor crea componentes wrapper y deja los originales intactos.

8 min de lectura · Publicado el: 26 mar 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog