Tematización
El sistema de diseño detrás de NachUI — tokens semánticos, el contrato de temas y cómo adaptarlos a tu marca.
NachUI no tiene colores hardcodeados. Cada componente se pinta con tokens semánticos —
bg-card, text-muted-foreground, border-rule — y esos tokens son variables CSS que te pertenecen. Cambiás una variable y todo el sistema se mueve con ella, en claro y en oscuro, sin tocar un solo componente.Cómo funciona
La tematización pasa por tres capas. Entenderlas es casi todo lo que necesitás.
1. Una variable CSS guarda el valor. Se declara en
:root y se sobrescribe dentro de .dark.2.
@theme inline la mapea al espacio de colores de Tailwind. Esto es lo que convierte una variable en utilidades.3. Tailwind genera las utilidades. Una vez que existe
--color-primary, tenés bg-primary, text-primary, border-primary, ring-primary y todas las demás, gratis.Como el paso 2 usa
inline, la utilidad resuelve la variable en el momento de uso en vez de hornear un valor fijo. Por eso alcanza con activar .dark en el <html> para recolorear toda la app, sin re-render.El modo oscuro se basa en una clase, declarada una sola vez:
Un token no es un color
Los tokens se nombran por rol, no por tono.
--destructive significa "esta acción es
peligrosa", no "esto es rojo". Mantener los nombres semánticos es lo que permite que un tema
cambie todos los valores y siga teniendo sentido.La convención foreground
Cada token de superficie viene en par con un token
-foreground para el contenido que va encima. La superficie se queda con el nombre pelado; su contenido lleva el sufijo.--cardes la superficie,--card-foregroundes el texto sobre esa superficie--primaryes el relleno,--primary-foregroundes la etiqueta sobre ese relleno
Si respetás el par, el contraste se resuelve solo: quien escribe el tema solo tiene que mantener cada par legible, y todos los componentes que usan ese par heredan esa garantía.
Referencia de tokens
Superficies
El orden de apilado de la interfaz, desde el lienzo hasta las capas flotantes.
| Token | Utilidad | Rol |
|---|---|---|
--background | bg-background | El lienzo de la página |
--card | bg-card | Paneles y tarjetas elevadas sobre el lienzo |
--popover | bg-popover | Capas flotantes: dropdowns, popovers, tooltips |
--surface-muted | bg-surface-muted | Zonas hundidas: cabeceras de tabla, filas en hover |
--background, --card y --popover tienen cada uno su -foreground.Texto
| Token | Utilidad | Rol |
|---|---|---|
--foreground | text-foreground | Texto principal |
--muted-strong | text-muted-strong | Texto secundario que igual hay que poder leer |
--muted-foreground | text-muted-foreground | Texto terciario: epígrafes, pistas, etiquetas inactivas |
--muted-strong está a propósito entre los otros dos. Los párrafos largos lo usan para que el cuerpo se lea más suave que los títulos sin caer al contraste de un epígrafe.Interacción
| Token | Utilidad | Rol |
|---|---|---|
--primary | bg-primary | La acción principal de una pantalla |
--secondary | bg-secondary | Acciones de apoyo |
--accent | bg-accent | Estados de hover y resalte sobre superficies neutras |
--muted | bg-muted | Zonas rellenas pero inertes |
--inverse | bg-inverse | Contraste invertido a propósito, por ejemplo tooltips |
Los cinco tienen su contraparte
-foreground.Estructura y foco
| Token | Utilidad | Rol |
|---|---|---|
--border | border-border | Bordes por defecto de los componentes |
--border-interactive | border-border-interactive | Bordes de controles con los que se puede interactuar |
--input | border-input | Bordes de campos de formulario |
--ring | ring-ring | Anillos de foco |
--rule | border-rule | Filetes editoriales: divisores de sección, rieles |
--grid-color | — | Fondos de puntos y grillas, usado directo en CSS |
--border y --rule suelen tener el mismo valor, pero están separados a propósito: uno es cromo de componente, el otro es estructura de página. Retocar los filetes de tu layout no debería tocar todas las tarjetas.Feedback
Cada uno de los cuatro tonos de feedback —
destructive, warning, success, info — trae una escala de cinco roles en vez de un solo color. Eso es lo que permite que un botón sólido y una alerta suave compartan tono sin que uno de los dos falle el contraste.| Sufijo | Ejemplo de utilidad | Rol |
|---|---|---|
| (ninguno) | bg-destructive | Relleno sólido, para botones |
-foreground | text-destructive-foreground | Contenido sobre ese relleno sólido |
-text | text-destructive-text | El tono como texto legible sobre superficie clara |
-surface | bg-destructive-surface | Fondo teñido para alertas y badges |
-border | border-destructive-border | Borde a juego con ese fondo teñido |
Sólido — el relleno lleva el significado:
Suave — lo lleva el tinte, y el texto se mantiene legible:
Usar
text-destructive sobre un fondo claro es el error típico: ese es el relleno del botón, calibrado para texto blanco, no para leerse. Usá -text en su lugar.Scrim, elevación y código
| Token | Utilidad | Rol |
|---|---|---|
--overlay | bg-overlay | Velo detrás de diálogos y drawers |
--elevation-sm / -md / -lg | shadow-sm / -md / -lg | La escala de sombras |
--code | bg-code | Superficie de los bloques de código |
--code-plain, --code-keyword, --code-string, --code-function, --code-number, --code-tag, --code-comment, --code-punctuation | — | Colores de sintaxis, leídos directo por el bloque de código |
Las sombras son valores tematizables, no drop shadows fijas: un tema oscuro les sube la opacidad, porque una sombra calibrada para un lienzo blanco desaparece sobre uno casi negro.
Radio
Un solo token base gobierna todas las esquinas del sistema.
La escala se deriva de ahí, así que cambiar la base reforma toda la app de manera proporcional:
| Utilidad | Derivación | Con el 1.5rem por defecto |
|---|---|---|
rounded-sm | calc(var(--radius) - 0.75rem) | 12px |
rounded-md | calc(var(--radius) - 0.5rem) | 16px |
rounded-lg | var(--radius) | 24px |
rounded-xl | calc(var(--radius) + 0.5rem) | 32px |
rounded-2xl | calc(var(--radius) + 1rem) | 40px |
Para esquinas más filosas en todos lados, bajá la base:
--radius es el único token declarado solo en :root — una esquina no tiene variante clara y oscura.Tipografía
Hay cuatro ranuras de fuente conectadas a Tailwind, así que
font-sans, font-heading, font-serif y font-mono resuelven a lo que vos cargues.En Next.js,
next/font define esas variables por vos:Después ponés la clase generada en el
<body> y los títulos la toman solos.Por qué OKLCH
Todos los tokens están escritos en OKLCH:
Es perceptualmente uniforme, y eso importa cuando construís una escala. En HSL,
hsl(60 100% 50%) y hsl(240 100% 50%) declaran la misma luminosidad mientras el amarillo se ve mucho más brillante que el azul. En OKLCH, luminosidad igual se ve igual de clara — así que mantener el valor L fijo entre tonos te da una paleta de feedback realmente equilibrada, en vez de una donde el badge de warning grita y el de info susurra.Además llega a colores que sRGB no puede expresar y degrada bien en navegadores viejos. oklch.com es un buen lugar para elegir valores.
Personalizar tu tema
Sobrescribir un token
Editá la variable en tu CSS. Cambiala en los dos bloques — un valor puesto solo en
:root se filtra al modo oscuro.No hay nada más que actualizar. Todo botón, link y anillo de foco que referencie
--primary va detrás.Agregar un token nuevo
Saltear el paso 2 es la falla habitual: la variable existe, pero
bg-brand nunca se genera y el elemento queda sin estilo.El contrato de temas
Un tema no es un puñado de colores: es el conjunto completo de tokens, declarado en ambos bloques,
:root y .dark. Esa lista vive en theme-contract.ts y está cubierta por un test.La regla existe por cómo falla la cascada acá. Un tema que omite
--success-surface no da error; hereda en silencio el valor de la paleta base, y terminás con un verde del tema por defecto filtrándose entre los colores de tu marca. Los tokens faltantes son invisibles hasta que alguien abre el único componente que los usa.Así que cuando escribas un tema, recorré el contrato:
- Superficies —
background,card,popover,surface-mutedy sus foregrounds - Interacción —
primary,secondary,accent,muted,inversey sus foregrounds, másmuted-strong - Estructura —
border,border-interactive,input,ring,rule,grid-color - Feedback — los cinco roles de
destructive,warning,successeinfo - Scrim y elevación —
overlay,elevation-sm,elevation-md,elevation-lg - Código —
codemás los ocho tokens de sintaxis - Radio —
--radius, solo en:root
Cambiar de tema
nachui init trae los temas disponibles y escribe el que elijas en tu hoja de estilos global, debajo de un marcador /* NachUI Tokens */.Volver a ejecutarlo reemplaza ese bloque y deja el resto de tu CSS intacto, así que podés probar un tema y arrepentirte. Para conservar una paleta que editaste a mano, mantené tus overrides debajo del marcador: todo lo que va después sobrevive.
Para ver cómo se aplica y se persiste la clase
.dark, mirá Modo Oscuro.¿Encontraste algo que mejorar?
¿Notaste un error, tipografía o detalle faltante en esta página? Ayúdanos a mejorar la documentación abriendo un issue en GitHub.