Construyendo un Sistema de Color para tu Design System
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
Las decisiones de color ad hoc se acumulan hasta convertirse en caos. Un azul de marca especificado como #3B82F6 en un componente, #2563EB en otro, y rgba(59, 130, 246, 0.8) en un tercero —todos supuestamente el "mismo" azul— crea una inconsistencia visual que erosiona la coherencia de marca, hace imposible implementar el modo oscuro de forma fiable y convierte cada cambio de color en un proyecto de arqueología en el código.
Un sistema de color resuelve esto estableciendo una única fuente de verdad para cada color de la interfaz, organizada en dos capas: una paleta primitiva con todos los valores de color disponibles, y una capa de tokens semánticos que mapea esas primitivas a sus roles previstos. Esta separación hace posible construir el modo oscuro, el modo de alto contraste y rediseños de marca cambiando las asignaciones de tokens semánticos, sin tocar ningún código de componente.
Esta guía describe cómo construir un sistema de color completo y listo para producción desde cero: elegir una paleta primitiva, generar escalas de tonos, definir tokens semánticos, implementarlos en CSS y Tailwind, y configurar documentación y pruebas.
Por Qué Importan los Colores Sistemáticos
El argumento a favor de los colores sistemáticos no es estético, es operacional. Esto es lo que falla sin un sistema:
La inconsistencia se acumula con el tiempo: Al inicio de un proyecto, diseñadores y desarrolladores toman decisiones de color ad hoc que "se ven bien". Después de dos años, el código acumula 40 valores de azul distintos que se supone son el mismo azul. Nadie sabe cuál es el correcto.
El modo oscuro requiere mapeo semántico: El modo oscuro no puede funcionar si los colores están codificados por su valor visual. color: #1E293B es un color oscuro que se ve bien como texto sobre un fondo claro. En modo oscuro, necesitas un valor diferente. Si codificaste #1E293B en 80 componentes, tienes 80 lugares que actualizar, o un sistema complejo de sobreescritura CSS que crea una nueva capa de deuda técnica. Los tokens semánticos eliminan esto.
Los cambios de tema son costosos: Cambiar la marca o el color primario debería ser un único cambio. Con un sistema semántico, cambias --color-primary-600 en el archivo de tokens y cada botón, enlace, anillo de foco y borde que lo referencia se actualiza automáticamente.
Las auditorías de accesibilidad fallan silenciosamente: Sin un sistema, verificar que todo el texto cumple los requisitos de contraste WCAG requiere auditar manualmente cada combinación texto/fondo. Con tokens semánticos, puedes definir reglas ("text-primary sobre surface-primary debe pasar AA") y probarlas programáticamente.
Convenciones de Nombres: Primitiva vs. Semántica
Un sistema de color bien estructurado tiene dos niveles de nombres con propósitos diferentes.
Tokens de Color Primitivos
Los tokens primitivos son los valores de color sin procesar, nombrados por su posición en una escala. No hacen suposiciones sobre el uso: son simplemente una paleta de colores disponibles:
blue-50 → #EFF6FF
blue-100 → #DBEAFE
blue-200 → #BFDBFE
blue-300 → #93C5FD
blue-400 → #60A5FA
blue-500 → #3B82F6
blue-600 → #2563EB
blue-700 → #1D4ED8
blue-800 → #1E40AF
blue-900 → #1E3A8A
blue-950 → #172554
Los nombres de tokens primitivos responden: "¿Qué color es este?" No responden: "¿Para qué sirve este color?"
Tokens de Color Semánticos
Los tokens semánticos referencian tokens primitivos y nombran los colores por su propósito:
color-action-primary → blue-600
color-action-primary-hover → blue-700
color-action-primary-text → white-0
color-surface-default → neutral-50 (modo claro) / neutral-900 (modo oscuro)
color-surface-elevated → white-0 (modo claro) / neutral-800 (modo oscuro)
color-text-primary → neutral-900 (modo claro) / neutral-50 (modo oscuro)
color-text-secondary → neutral-600 (modo claro) / neutral-400 (modo oscuro)
color-text-disabled → neutral-400 (modo claro) / neutral-600 (modo oscuro)
color-border-default → neutral-200 (modo claro) / neutral-700 (modo oscuro)
color-border-focused → blue-500
Los nombres de tokens semánticos responden: "¿Para qué sirve este color?" No especifican un valor, sino que referencian una primitiva.
Esta separación es la decisión estructural clave. Los componentes referencian tokens semánticos, no primitivas. Cuando defines el modo oscuro, reasignas los tokens semánticos a diferentes primitivas: ningún código de componente cambia.
Esquemas de Nombres en Uso Real
Diferentes design systems usan diferentes convenciones de nombres para su capa semántica:
| Sistema | Ejemplo de Nombre Semántico | Convención |
|---|---|---|
| Material Design | sys.color.primary |
Jerarquía de roles separada por puntos |
| Apple HIG | label, secondaryLabel |
Nombres de rol simples |
| Radix UI | --accent-9, --gray-11 |
Nombres semánticos basados en escala |
| Primer (GitHub) | fgColor.default, bgColor.default |
Rol + variante |
La convención importa menos que la consistencia. Elige un esquema y aplícalo en todo.
Generación de Escala de Tonos (50–950)
Una escala de tonos es una serie ordenada de valores de color desde casi blanco hasta casi negro dentro de una misma familia de matiz. Tailwind CSS popularizó la escala 50–950; Material Design usa 100–900. Los números específicos importan menos que tener suficientes pasos para cubrir el rango de casos de uso: tintes muy claros para fondos, valores intermedios para elementos interactivos y tonos profundos para texto.
Qué Hace Buena a una Escala de Tonos
Una escala de tonos utilizable tiene dos propiedades:
- Pasos perceptuales suficientes: Los tonos adyacentes deben ser visualmente distintos: no tan cercanos que la diferencia sea imperceptible, ni tan separados que aparezcan huecos en la cobertura.
- Progresión de luminosidad consistente: Los valores de luminosidad deben aumentar de forma predecible del 950 (más oscuro) al 50 (más claro), sin saltos ni reversiones sorpresivos.
El modo de fallo de las escalas de tonos seleccionadas a mano es el espaciado irregular de luminosidad. Una escala donde los tonos 300, 400 y 500 son perceptualmente similares y luego hay un salto dramático de 500 a 700 no es útil: siempre recurrirás al 500 cuando necesites algo en el medio del rango.
Generando Tonos en OKLCH
OKLCH (OK Luminosidad-Croma-Matiz) es el mejor espacio de color para la generación de tonos porque su canal de luminosidad es perceptualmente uniforme: pasos numéricos iguales producen pasos perceptuales iguales. Si espacias los valores de luminosidad de L=0,15 (muy oscuro) a L=0,98 (muy claro) en incrementos iguales, los tonos resultantes se verán igualmente espaciados visualmente.
Para generar una escala de 11 tonos en OKLCH:
- Elige tu matiz y croma base: p. ej., H=250 (azul), C=0,20.
- Espaciar los valores de luminosidad de forma uniforme: L = 0,15, 0,22, 0,30, 0,38, 0,47, 0,55, 0,63, 0,72, 0,82, 0,92, 0,97.
- Opcionalmente, varía el croma ligeramente: reduce el croma en los extremos (muy claro y muy oscuro) donde los colores saturados se ven fuera de lugar.
- Convierte los valores OKLCH a hexadecimal para la implementación.
Una escala práctica de OKLCH a hexadecimal para un matiz azul:
Tono | OKLCH | Hex
------+--------------------------+--------
50 | oklch(0.97 0.04 250) | #EFF6FF
100 | oklch(0.92 0.07 250) | #DBEAFE
200 | oklch(0.84 0.11 250) | #BFDBFE
300 | oklch(0.74 0.16 250) | #93C5FD
400 | oklch(0.65 0.19 250) | #60A5FA
500 | oklch(0.55 0.22 250) | #3B82F6
600 | oklch(0.47 0.22 250) | #2563EB
700 | oklch(0.40 0.20 250) | #1D4ED8
800 | oklch(0.32 0.18 250) | #1E40AF
900 | oklch(0.24 0.14 250) | #1E3A8A
950 | oklch(0.17 0.10 250) | #172554
Usa el Generador de Tonos de ColorFYI para generar escalas de tonos estilo Tailwind desde cualquier color base, y el Conversor de Color para inspeccionar cada tono generado en el espacio OKLCH y verificar la progresión de luminosidad.
¿Cuántos Pasos de Tono?
Para la mayoría de los design systems, 9–11 pasos es el rango correcto:
- 50 y 950: Valores extremos para fondos de superficie y uso de texto/borde más profundo.
- 100–200: Fondos con tinte claro (fondos de alertas, estados hover sobre superficies blancas).
- 300–400: Colores de iconos, texto de marcador de posición, estados deshabilitados.
- 500: A menudo el color de marca "puro", el que especifican los estándares de color de marca.
- 600–700: Rellenos de elementos interactivos (botones, enlaces) donde se requiere contraste sobre blanco.
- 800–900: Uso de texto y texto sobre fondos de color.
Genera tonos para cada familia de matiz en tu paleta: tu color de marca principal, una serie de grises neutros y cualquier color de acento (rojo/error, verde/éxito, amarillo/advertencia, etc.).
Tokens de Color Semánticos
Con las primitivas establecidas, define tokens semánticos que mapeen las primitivas a roles. La granularidad de este mapeo determina la flexibilidad del sistema.
Colores de Superficie
Los tokens de superficie definen las capas de fondo de tu interfaz de usuario:
:root {
--color-surface-base: var(--neutral-50); /* Fondo de página */
--color-surface-elevated: var(--white); /* Fondos de tarjetas y hojas */
--color-surface-overlay: var(--white); /* Fondos de modales y popovers */
--color-surface-sunken: var(--neutral-100); /* Campos incrustados, bloques de código */
}
[data-theme="dark"] {
--color-surface-base: var(--neutral-950);
--color-surface-elevated: var(--neutral-900);
--color-surface-overlay: var(--neutral-800);
--color-surface-sunken: var(--neutral-950);
}
Colores de Texto
Los tokens de texto cubren cada rol de texto en la jerarquía:
:root {
--color-text-primary: var(--neutral-900); /* Texto de cuerpo, encabezados */
--color-text-secondary: var(--neutral-600); /* Leyendas, etiquetas */
--color-text-tertiary: var(--neutral-400); /* Marcador de posición, sugerencia */
--color-text-disabled: var(--neutral-300); /* Estado deshabilitado */
--color-text-inverse: var(--white); /* Texto sobre fondos oscuros o de color */
--color-text-link: var(--blue-600);
--color-text-link-hover: var(--blue-700);
}
[data-theme="dark"] {
--color-text-primary: var(--neutral-50);
--color-text-secondary: var(--neutral-400);
--color-text-tertiary: var(--neutral-600);
--color-text-disabled: var(--neutral-700);
--color-text-link: var(--blue-400);
--color-text-link-hover: var(--blue-300);
}
Colores Interactivos / de Acción
Los tokens de acción cubren cada estado de los elementos interactivos:
:root {
/* Acción primaria */
--color-action-primary-bg: var(--blue-600);
--color-action-primary-bg-hover: var(--blue-700);
--color-action-primary-bg-active: var(--blue-800);
--color-action-primary-text: var(--white);
/* Acción secundaria / fantasma */
--color-action-secondary-border: var(--blue-600);
--color-action-secondary-text: var(--blue-600);
--color-action-secondary-bg-hover: var(--blue-50);
/* Acción destructiva */
--color-action-danger-bg: var(--red-600);
--color-action-danger-bg-hover: var(--red-700);
--color-action-danger-text: var(--white);
}
Colores de Retroalimentación / Estado
Los tokens de estado mapean los colores funcionales a su significado semántico:
:root {
/* Éxito */
--color-status-success-bg: var(--green-50);
--color-status-success-border: var(--green-200);
--color-status-success-text: var(--green-700);
--color-status-success-icon: var(--green-500);
/* Advertencia */
--color-status-warning-bg: var(--yellow-50);
--color-status-warning-border: var(--yellow-200);
--color-status-warning-text: var(--yellow-800);
--color-status-warning-icon: var(--yellow-500);
/* Error */
--color-status-error-bg: var(--red-50);
--color-status-error-border: var(--red-200);
--color-status-error-text: var(--red-700);
--color-status-error-icon: var(--red-500);
/* Información */
--color-status-info-bg: var(--blue-50);
--color-status-info-border: var(--blue-200);
--color-status-info-text: var(--blue-700);
--color-status-info-icon: var(--blue-500);
}
Colores de Borde
:root {
--color-border-default: var(--neutral-200);
--color-border-strong: var(--neutral-400);
--color-border-focused: var(--blue-500);
--color-border-error: var(--red-500);
--color-border-disabled: var(--neutral-200);
}
[data-theme="dark"] {
--color-border-default: var(--neutral-700);
--color-border-strong: var(--neutral-500);
--color-border-focused: var(--blue-400);
--color-border-error: var(--red-400);
--color-border-disabled: var(--neutral-700);
}
Implementación en CSS
La implementación en CSS usa propiedades personalizadas definidas a nivel :root (o en selectores [data-theme] para la temática). Los componentes referencian tokens semánticos, nunca primitivas directamente.
Estructura de Archivos
design-system/
├── tokens/
│ ├── primitives.css # Todos los valores de color primitivos
│ ├── semantic.css # Mapeo token semántico → primitiva
│ └── dark.css # Sobreescrituras semánticas para modo oscuro
└── components/
├── button.css
├── card.css
└── ...
primitives.css
:root {
/* Escala neutral */
--neutral-50: #F8FAFC;
--neutral-100: #F1F5F9;
--neutral-200: #E2E8F0;
--neutral-300: #CBD5E1;
--neutral-400: #94A3B8;
--neutral-500: #64748B;
--neutral-600: #475569;
--neutral-700: #334155;
--neutral-800: #1E293B;
--neutral-900: #0F172A;
--neutral-950: #020617;
/* Escala azul */
--blue-50: #EFF6FF;
--blue-100: #DBEAFE;
--blue-200: #BFDBFE;
--blue-300: #93C5FD;
--blue-400: #60A5FA;
--blue-500: #3B82F6;
--blue-600: #2563EB;
--blue-700: #1D4ED8;
--blue-800: #1E40AF;
--blue-900: #1E3A8A;
--blue-950: #172554;
/* Blanco y negro */
--white: #FFFFFF;
--black: #000000;
}
Uso en Componentes
Los componentes referencian solo tokens semánticos:
.button-primary {
background-color: var(--color-action-primary-bg);
color: var(--color-action-primary-text);
border: none;
}
.button-primary:hover {
background-color: var(--color-action-primary-bg-hover);
}
.button-primary:active {
background-color: var(--color-action-primary-bg-active);
}
.button-primary:disabled {
background-color: var(--neutral-200); /* Excepción: el estado deshabilitado suele usar primitivas */
color: var(--color-text-disabled);
}
Cuando el tema cambia de claro a oscuro, --color-action-primary-bg se resuelve automáticamente a un valor diferente: no se necesitan sobreescrituras de modo oscuro específicas del botón.
Implementación en Tailwind CSS
Tailwind v3 y v4 admiten la integración de tokens de diseño a través del archivo de configuración y el enfoque de variables CSS.
Configuración de Tailwind v3
Define tu escala de colores en tailwind.config.js y mapea a propiedades personalizadas CSS:
// tailwind.config.js
const colors = require("tailwindcss/colors");
module.exports = {
theme: {
colors: {
transparent: "transparent",
current: "currentColor",
white: "#ffffff",
black: "#000000",
// Escalas primitivas
neutral: {
50: "#F8FAFC",
100: "#F1F5F9",
200: "#E2E8F0",
300: "#CBD5E1",
400: "#94A3B8",
500: "#64748B",
600: "#475569",
700: "#334155",
800: "#1E293B",
900: "#0F172A",
950: "#020617",
},
blue: {
50: "#EFF6FF",
100: "#DBEAFE",
200: "#BFDBFE",
300: "#93C5FD",
400: "#60A5FA",
500: "#3B82F6",
600: "#2563EB",
700: "#1D4ED8",
800: "#1E40AF",
900: "#1E3A8A",
950: "#172554",
},
// Tokens semánticos mediante variables CSS
"action-primary": "var(--color-action-primary-bg)",
"action-primary-hover": "var(--color-action-primary-bg-hover)",
"surface": "var(--color-surface-base)",
"surface-elevated": "var(--color-surface-elevated)",
"text-primary": "var(--color-text-primary)",
"text-secondary": "var(--color-text-secondary)",
"border-default": "var(--color-border-default)",
},
},
};
Con esta configuración, puedes escribir bg-action-primary y se resuelve al valor del token semántico correcto, que a su vez se resuelve a la primitiva correcta para el tema actual.
Configuración CSS-First de Tailwind v4
Tailwind v4 reemplaza la configuración JavaScript con un enfoque CSS-first usando @theme:
@import "tailwindcss";
@theme {
/* Colores primitivos */
--color-blue-500: #3B82F6;
--color-blue-600: #2563EB;
--color-blue-700: #1D4ED8;
--color-neutral-50: #F8FAFC;
--color-neutral-900: #0F172A;
/* Tokens semánticos */
--color-surface: var(--color-surface-base);
--color-text: var(--color-text-primary);
--color-primary: var(--color-action-primary-bg);
}
/* Definiciones semánticas */
:root {
--color-surface-base: var(--color-neutral-50);
--color-text-primary: var(--color-neutral-900);
--color-action-primary-bg: var(--color-blue-600);
}
[data-theme="dark"] {
--color-surface-base: oklch(0.145 0 0);
--color-text-primary: var(--color-neutral-50);
--color-action-primary-bg: var(--color-blue-500);
}
Pruebas y Documentación
Un sistema de color solo es útil si todo el equipo lo comprende y lo implementa de forma confiable. Las pruebas detectan regresiones; la documentación hace que el sistema sea aprendible.
Pruebas de Contraste
Cada token de texto emparejado con su token de superficie de fondo previsto debe verificarse programáticamente para cumplir los requisitos WCAG 2.1 AA (4,5:1 para texto normal, 3:1 para texto grande). Escribe pruebas que:
- Resuelvan los valores actuales de la propiedad CSS personalizada (tanto para temas claros como oscuros).
- Calculen la relación de contraste WCAG entre cada par texto/superficie.
- Afirmen que la relación cumple el umbral requerido.
import { getContrastRatio } from "some-color-utility";
const tokens = {
light: {
textPrimary: "#0F172A",
surfaceBase: "#F8FAFC",
},
dark: {
textPrimary: "#F8FAFC",
surfaceBase: "#020617",
}
};
describe("Contraste del sistema de color", () => {
test("text-primary sobre surface-base pasa AA en modo claro", () => {
const ratio = getContrastRatio(tokens.light.textPrimary, tokens.light.surfaceBase);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});
test("text-primary sobre surface-base pasa AA en modo oscuro", () => {
const ratio = getContrastRatio(tokens.dark.textPrimary, tokens.dark.surfaceBase);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});
});
Usa el Verificador de Contraste de ColorFYI para la verificación manual de pares de tokens individuales durante la definición de tokens, antes de escribir pruebas automatizadas.
Documentación de Tokens de Color
Documenta cada token semántico con: - Nombre: El nombre exacto de la variable CSS. - Valor (claro): La primitiva que referencia en modo claro. - Valor (oscuro): La primitiva en modo oscuro. - Uso: Dónde debe usarse este token. - Antipatrones: Dónde no debe usarse.
| Token | Claro | Oscuro | Uso |
|------------------------------|----------------|----------------|--------------------------------|
| --color-text-primary | neutral-900 | neutral-50 | Texto de cuerpo, encabezados |
| --color-text-secondary | neutral-600 | neutral-400 | Leyendas, metadatos, etiquetas |
| --color-action-primary-bg | blue-600 | blue-500 | Fondo del botón primario |
| --color-surface-elevated | white | neutral-900 | Fondos de tarjetas y paneles |
Pruebas de Regresión Visual
Combina la documentación de tokens con pruebas de regresión visual usando herramientas como Percy, Chromatic o la comparación de capturas de pantalla de Playwright. Una prueba de regresión visual renderiza tu biblioteca de componentes en cada cambio de token, captura pantallas y las compara con una línea base: cualquier cambio de color desencadena una revisión, detectando regresiones accidentales.
Página de Paleta de Colores en Storybook
En Storybook, crea una historia dedicada de paleta de colores que renderice todos los tokens como muestras:
// ColorPalette.stories.jsx
const tokens = [
{ name: "--color-surface-base", label: "Superficie / Base" },
{ name: "--color-surface-elevated", label: "Superficie / Elevada" },
{ name: "--color-text-primary", label: "Texto / Primario" },
// ...
];
export function ColorPaletteDoc() {
return (
<div>
{tokens.map(({ name, label }) => (
<div key={name} style={{ display: "flex", alignItems: "center", gap: 16 }}>
<div style={{
width: 48,
height: 48,
backgroundColor: `var(${name})`,
borderRadius: 4,
border: "1px solid rgba(0,0,0,0.1)"
}} />
<div>
<div>{label}</div>
<code>{name}</code>
</div>
</div>
))}
</div>
);
}
Esto renderiza los valores reales del token en vivo, en cualquier tema que esté activo en Storybook, haciendo que la documentación sea autovalidable en lugar de estática.
Conclusiones Clave
- Un sistema de color separa los tokens primitivos (nombrados por su posición en la escala:
blue-600) de los tokens semánticos (nombrados por su propósito:color-action-primary-bg). Los componentes siempre referencian tokens semánticos, nunca primitivas. - Esta separación habilita el modo oscuro y la temática al reasignar tokens semánticos a diferentes primitivas a nivel de propiedad CSS personalizada, sin cambios en el código de componentes.
- Genera escalas de tonos (50–950) usando el espacio de color OKLCH donde el canal de luminosidad es perceptualmente uniforme: pasos numéricos iguales producen resultados visuales igualmente espaciados. Usa el Generador de Tonos de ColorFYI para crear escalas compatibles con Tailwind desde cualquier color ancla.
- Las categorías de tokens semánticos cubren cuatro áreas: superficie (capas de fondo), texto (jerarquía de roles de texto), acción (estados de elementos interactivos) y retroalimentación (éxito, advertencia, error, información).
- En CSS, define las primitivas en un archivo y los mapeos semánticos en otro. Usa sobreescrituras del selector
[data-theme="dark"]para reasignar tokens semánticos en modo oscuro. - En Tailwind v3, extiende el objeto de color de la configuración con referencias de propiedades CSS personalizadas. En Tailwind v4, usa el bloque CSS-first
@themepara definir tanto tokens primitivos como semánticos. - Prueba cada par de tokens texto/superficie para el cumplimiento de contraste WCAG, de forma programática, no solo manualmente. Usa el Verificador de Contraste de ColorFYI para verificación manual durante la definición de tokens.
- Documenta los tokens en Storybook con muestras en vivo que rendericen los valores reales de la propiedad CSS personalizada, asegurando que la documentación refleje el estado real actual del sistema.
Colores relacionados
Marcas relacionadas
Herramientas relacionadas
Generador de paletas
Genera paletas de colores armoniosas usando esquemas complementarios, análogos, triádicos y complementarios divididos.
Generador de tonos
Genera escalas de tonos estilo Tailwind CSS (50–950) a partir de cualquier color base para sistemas de diseño.