Generación dinámica de color: algoritmos para sistemas de diseño
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.
Los sistemas de diseño generan cada vez más sus paletas de color de manera programática en lugar de seleccionar cada muestra a mano. Un solo color de marca bien elegido puede generar todo un sistema: una escala tonal completa, emparejamientos de texto accesibles, una variante para modo oscuro y tonos complementarios armoniosos. Los algoritmos subyacentes determinan si el resultado luce profesionalmente elaborado o computacionalmente rígido.
Este tutorial explica los algoritmos centrales detrás de la generación dinámica de color — escalas de tonos, selección con conciencia de contraste, derivación de temas, modo oscuro automático y las bibliotecas que implementan estas técnicas — con ejemplos prácticos que puedes aplicar a tu propio sistema de diseño.
Algoritmos de generación de tonos
Una escala de tonos es una secuencia sistemática de variantes tonales para un solo tono: desde casi blanco en el extremo claro hasta casi negro en el extremo oscuro. La escala 50–950 de Tailwind CSS es el ejemplo más familiar. La calidad de una escala de tonos depende completamente del algoritmo usado para generarla.
El problema con la generación de tonos basada en HSL
El enfoque naive — partir de un color HSL e incrementar la luminosidad en pasos iguales — produce escalas de aspecto desigual. Debido a que el eje de luminosidad de HSL no es perceptualmente uniforme, un paso de L=40 a L=50 luce diferente dependiendo del tono. El amarillo cambia dramáticamente; el azul apenas se mueve. La escala resultante luce hecha a mano e inconsistente.
// Enfoque naive — NO hacer esto
function hslShades(hue, saturation, steps = 10) {
return Array.from({ length: steps }, (_, i) => {
const lightness = 5 + (i / (steps - 1)) * 90;
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
});
}
La salida luce aceptable para amarillos y verdes de rango medio, pero se degrada mal para azules, morados y tonos desaturados.
Generación de tonos basada en OKLCH
El enfoque correcto usa OKLCH, donde el eje L está calibrado perceptualmente. Los incrementos iguales de L producen pasos de aspecto igual sin importar el tono. El algoritmo es:
- Convierte el color de marca base a OKLCH
- Define el rango de luminosidad (p. ej., L = 0.97 en el tono 50, hasta L = 0.15 en el tono 950)
- Interpola el croma junto con la luminosidad — los tonos muy claros y muy oscuros tienen un croma inherentemente menor que los tonos medios
- Mantén el tono constante (o aplica una pequeña rotación de tono para cambios estéticos de temperatura cálida/fría)
- Convierte cada punto OKLCH de vuelta a HEX o sRGB
// Generación de tonos basada en OKLCH
function oklchShades(baseHex) {
const { L, C, H } = hexToOklch(baseHex);
// Paradas de luminosidad para una escala de 11 pasos (50–950)
const lightnessStops = [0.97, 0.93, 0.86, 0.78, 0.68, 0.58, 0.48, 0.39, 0.30, 0.22, 0.15];
// Curva de croma: alcanza su máximo en el croma del color base, se reduce hacia los extremos
const chromaStops = lightnessStops.map(l => {
const distance = Math.abs(l - L);
const falloff = Math.max(0, 1 - distance * 1.5);
return C * falloff;
});
return lightnessStops.map((l, i) => ({
oklch: `oklch(${l} ${chromaStops[i].toFixed(3)} ${H})`,
hex: oklchToHex(l, chromaStops[i], H),
}));
}
La curva de reducción de croma es el parámetro de ajuste más importante. Permitir el croma completo en el extremo oscuro produce tonos que lucen artificialmente saturados y oscuros. Reducir el croma a medida que te acercas al extremo claro evita pasteles desteñidos con muy poco carácter de color. La forma exacta de la reducción es una decisión de diseño — algunos sistemas usan una curva cuadrática, otros una función lineal por tramos.
Usa el Generador de tonos para ver este algoritmo aplicado a cualquier color de marca. Produce una escala compatible con Tailwind con valores predeterminados accesibles.
Rotación de tono para cambios de temperatura
Muchos sistemas de diseño aplican una pequeña rotación de tono junto con la luminosidad — los tonos más claros se desplazan ligeramente hacia lo cálido (valores H más bajos hacia el amarillo), los tonos más oscuros se desplazan ligeramente hacia lo frío (valores H más altos hacia el azul/violeta). Esto imita cómo se comportan realmente los materiales físicos: un azul marino oscuro luce ligeramente más cálido en sus tintes claros.
// Añadir rotación sutil de tono
function shadeWithHueShift(L_target, C_target, H_base, L_base) {
const lightnessDelta = L_target - L_base;
// Desplazar hasta ±10 grados: cálido (−) para tonos claros, frío (+) para tonos oscuros
const hueShift = -lightnessDelta * 15;
return { L: L_target, C: C_target, H: H_base + hueShift };
}
La magnitud del desplazamiento de tono es una elección estilística. La propia paleta de Tailwind aplica desplazamientos sutiles de tono. Las escalas lineales (sin desplazamiento) lucen limpias pero pueden sentirse mecánicas.
Selección de color con conciencia de contraste
Generar una hermosa escala de tonos es solo la mitad del problema. Para que cada tono sea utilizable como color de texto o fondo, necesitas saber con qué tono de la escala emparejarlo para obtener un contraste accesible.
Algoritmo: encontrar el tono de aprobación de contraste mínimo
Dado un color de fondo y una escala de tonos, encuentra el tono más oscuro de la escala que pasa WCAG AA (4.5:1 para texto normal):
function findAccessibleTextShade(backgroundHex, shadeScale) {
const bgLuminance = relativeLuminance(backgroundHex);
for (const shade of shadeScale) {
const shadeLuminance = relativeLuminance(shade.hex);
const ratio = contrastRatio(bgLuminance, shadeLuminance);
if (ratio >= 4.5) {
return shade; // Primer tono que pasa
}
}
return shadeScale[shadeScale.length - 1]; // El más oscuro disponible
}
function contrastRatio(L1, L2) {
const lighter = Math.max(L1, L2);
const darker = Math.min(L1, L2);
return (lighter + 0.05) / (darker + 0.05);
}
Para la mayoría de los colores de marca prácticos, el tono 700 u 800 de la escala pasará AA contra blanco, y el tono 50 o 100 pasará AA contra el tono más oscuro de la escala.
Generación de tokens de texto accesibles
Un sistema de tokens accesible completo empareja cada tono de fondo con al menos un tono de primer plano que pasa WCAG AA. El patrón común:
function generateAccessiblePairs(shadeScale) {
const pairs = {};
shadeScale.forEach((shade, index) => {
// Encontrar colores de texto que pasan para este fondo
const passingShades = shadeScale.filter(textShade => {
const ratio = contrastRatio(
relativeLuminance(shade.hex),
relativeLuminance(textShade.hex)
);
return ratio >= 4.5;
});
pairs[shade.name] = {
background: shade.hex,
textPrimary: passingShades[passingShades.length - 1]?.hex, // El más oscuro que pasa
textSecondary: passingShades[passingShades.length - 3]?.hex, // Ligeramente más claro
};
});
return pairs;
}
Para una escala de azul donde el color de marca es #2563EB (blue-600): - Fondo blue-50 → texto principal: blue-900, texto secundario: blue-700 - Fondo blue-600 → texto principal: blanco (#FFFFFF) o blue-50 - Fondo blue-900 → texto principal: blanco (#FFFFFF) o blue-100
El Generador de tonos muestra las relaciones de contraste junto a cada tono generado para que puedas identificar inmediatamente qué pares funcionan.
Generación de tema a partir de un color de marca
Dado un solo color de marca — el que aparece en el logo y los materiales de marketing de una empresa — puedes derivar un tema completo del sistema de diseño: roles de color primario, neutro y semántico.
Paso 1: Anclar la escala primaria
Extrae los valores OKLCH del color de marca y genera la escala completa de tonos como se describió anteriormente. Establece el color de marca como tono 600 (o donde sea que se sitúe en el rango de brillo percibido — típicamente el rango 500–700 para la mayoría de los colores de marca).
Paso 2: Derivar una escala neutra
La mayoría de los sistemas de diseño necesitan un gris neutro que se sienta armónico con el color de marca. Un neutro cálido se deriva tomando el tono del color de marca y desaturándolo en gran medida:
function deriveNeutralScale(brandOklch) {
const { H } = brandOklch;
// Usa el tono de marca con un croma muy bajo — lo suficiente para sentirse cálido/frío
const neutralBaseChroma = 0.010; // Casi gris, leve influencia de tono
return generateOklchShades({
L: 0.55, C: neutralBaseChroma, H: H
});
}
Para una marca de azul (#2563EB, tono ≈ 260°), esto genera una escala neutra ligeramente azul-grisácea — más fría que el gris puro, armonizando con el color de marca. Para una marca naranja, los neutros se inclinan hacia lo cálido.
Paso 3: Asignar roles semánticos
Los tokens semánticos mapean las escalas generadas a roles funcionales:
:root {
/* Generado desde el azul de marca #2563EB */
--color-primary-50: /* escala azul 50 */;
--color-primary-600: /* escala azul 600 = color de marca */;
--color-primary-900: /* escala azul 900 */;
/* Generado desde el tono de marca + desaturado (neutro cálido) */
--color-neutral-50: /* escala neutra 50 */;
--color-neutral-900: /* escala neutra 900 */;
/* Asignaciones semánticas */
--color-bg-base: var(--color-neutral-50);
--color-text-primary: var(--color-neutral-900);
--color-action-primary: var(--color-primary-600);
--color-action-primary-hover: var(--color-primary-700);
--color-action-primary-text: #FFFFFF;
}
Paso 4: Generar colores de acento complementarios
Para una paleta completa, puedes querer 1–2 colores de acento que armonicen con la marca. El Generador de paletas puede calcular relaciones armónicas — complementarias (opuestas en el círculo cromático), triádicas o análogas.
Programáticamente, rotar el tono OKLCH en desplazamientos fijos te da compañeros armoniosos con luminosidad y croma igualados:
function generateCompanions(brandOklch, scheme = 'triadic') {
const { L, C, H } = brandOklch;
const rotations = {
complementary: [180],
triadic: [120, 240],
analogous: [30, -30],
split_complementary: [150, 210],
};
return rotations[scheme].map(rotation => ({
L, C,
H: (H + rotation + 360) % 360
}));
}
Dado que L y C de OKLCH son independientes del tono, los colores compañeros generados de esta manera tienen la misma luminosidad y croma que el color de marca — están automáticamente equilibrados perceptualmente, sin necesidad de ajuste manual.
Paleta automática de modo oscuro
Generar una paleta de modo oscuro a partir de una paleta de modo claro no es simplemente una cuestión de invertir las asignaciones de tonos. Una paleta de modo oscuro requiere:
- Fondos más oscuros (obviamente)
- Texto más claro que mantenga las mismas relaciones de contraste que el texto en modo claro
- Colores primarios ajustados — los tonos de marca de tono medio (500–600) a menudo lucen correctos en fondos claros pero aparecen excesivamente brillantes o saturados en los oscuros
- Croma reducido en algunas áreas — las superficies oscuras con croma alto pueden lucir agresivas
Algoritmo: generación automática de tokens de modo oscuro
El algoritmo confiable más simple mapea los índices de tonos del modo claro a sus equivalentes en modo oscuro usando una función de reflexión:
// Modo claro: el fondo usa tonos claros, el texto usa tonos oscuros
// Modo oscuro: invertir la relación fondo/texto
const LIGHT_TO_DARK_MAP = {
// Tonos de fondo: reflejar
50: 900, 100: 800, 200: 700,
// Tonos de rango medio: ajustar ligeramente
300: 700, 400: 600, 500: 500,
600: 400, 700: 300, 800: 200,
// Tonos de texto: reflejar
900: 100, 950: 50,
};
function generateDarkTokens(lightTokens, shadeScale) {
return Object.entries(lightTokens).reduce((dark, [token, shade]) => {
const darkShadeIndex = LIGHT_TO_DARK_MAP[shade.index];
dark[token] = shadeScale[darkShadeIndex];
return dark;
}, {});
}
Reducción de croma para superficies oscuras
Los fondos oscuros con colores de acento de croma completo pueden lucir discordantes. Una heurística práctica reduce el croma para colores usados como tokens de fondo o superficie en modo oscuro, mientras lo preserva para colores interactivos (botones, enlaces):
function darkSurfaceColor(oklch) {
return {
...oklch,
L: oklch.L * 0.3, // Mucho más oscuro
C: oklch.C * 0.6, // Reducir croma para evitar dureza
};
}
function darkInteractiveColor(oklch) {
return {
...oklch,
L: Math.min(0.75, oklch.L + 0.15), // Más claro para mayor visibilidad
C: oklch.C, // Mantener croma completo para vibración
};
}
Por ejemplo, partiendo de un azul de marca #2563EB (≈ oklch(0.55 0.21 264)):
- Botón de acción en modo claro: oklch(0.55 0.21 264) → #2563EB
- Botón de acción en modo oscuro: oklch(0.70 0.21 264) → aproximadamente #60A5FA (blue-400) — más brillante para destacar sobre fondos oscuros sin perder saturación
Verifica las relaciones de contraste del modo oscuro usando el mismo algoritmo que el modo claro. El texto blanco (#FFFFFF) sobre colores de superficie en modo oscuro a menudo necesita ser reemplazado por variantes casi-blancas de la escala neutra de la marca — --color-neutral-100 o --color-neutral-50 — en lugar de blanco puro, que puede lucir duro en pantallas OLED.
Implementación en CSS: variables de modo oscuro
:root {
/* Modo claro */
--color-bg-base: oklch(0.97 0.01 264);
--color-bg-surface: oklch(1.00 0.00 0);
--color-text-primary: oklch(0.18 0.03 264);
--color-action-primary: oklch(0.55 0.21 264);
}
@media (prefers-color-scheme: dark) {
:root {
/* Modo oscuro generado automáticamente */
--color-bg-base: oklch(0.16 0.02 264);
--color-bg-surface: oklch(0.22 0.02 264);
--color-text-primary: oklch(0.93 0.01 264);
--color-action-primary: oklch(0.70 0.21 264);
}
}
Bibliotecas y herramientas
Varias bibliotecas maduras implementan estos algoritmos con calidad de producción:
Bibliotecas de generación de color
Radix UI Colors — Escalas de colores de código abierto generadas algorítmicamente con modo oscuro automático y emparejamientos probados con WCAG. Usa un algoritmo basado en CIELAB. El sistema de código abierto más probado en producción.
npm install @radix-ui/colors
import { blue, blueDark } from '@radix-ui/colors';
// blue.blue9 — el azul más vívido, modo claro
// blueDark.blue9 — el equivalente en modo oscuro
Palette.js / Culori — Una biblioteca JavaScript de bajo nivel para colores con soporte completo de OKLCH, útil para construir tus propios algoritmos:
import { oklch, formatHex, interpolate, samples } from 'culori';
const brandOklch = oklch('#2563EB');
const scale = samples(11).map(t => ({
t,
color: formatHex({
mode: 'oklch',
l: 0.15 + t * 0.82, // L de 0.15 (oscuro) a 0.97 (claro)
c: brandOklch.c * Math.sin(t * Math.PI), // El croma alcanza su máximo en el tono medio
h: brandOklch.h,
})
}));
ColorBox — La herramienta de código abierto de Lyft (originalmente de Javier Arce). Genera escalas de color accesibles con un editor visual de curvas de luminosidad y saturación. Proporciona una interfaz visual para lo que este tutorial describe algorítmicamente.
Integraciones con herramientas de diseño
Plugin de tokens para Figma — Trabaja con tokens de diseño en formato JSON. Puedes definir tokens basados en OKLCH y sincronizarlos con propiedades personalizadas de CSS.
Style Dictionary — El transformador de tokens de diseño de código abierto de Amazon. Útil para convertir un archivo de definición de tokens a propiedades personalizadas CSS, variables SCSS, constantes de iOS/Android u otro formato de plataforma.
{
"color": {
"primary": {
"600": { "value": "oklch(0.55 0.21 264)", "comment": "Azul de marca" }
}
}
}
Herramientas en línea
El Generador de tonos en ColorFYI aplica la generación de tonos con conciencia de OKLCH a cualquier color de marca y produce una escala compatible con Tailwind con relaciones de contraste. El Generador de paletas calcula armonías de color complementarias, triádicas y análogas.
Conclusiones clave
- La generación de tonos basada en HSL produce escalas desiguales porque la luminosidad de HSL no es perceptualmente uniforme. Los incrementos iguales de L lucen muy diferentes dependiendo del tono.
- La generación de tonos basada en OKLCH produce pasos visualmente uniformes al explotar el eje L calibrado. Incluye una curva de reducción de croma — reduce el croma hacia los extremos de la escala para obtener tintes y sombras de aspecto natural.
- La generación de tokens con conciencia de contraste empareja cada tono de fondo con el tono de texto de contraste mínimo que pasa, usando la fórmula de luminancia relativa de las WCAG. Siempre verifica las relaciones en lugar de asumir que son adecuadas.
- La generación de temas a partir de un color de marca produce: (1) una escala primaria anclada al tono de la marca, (2) una escala neutra armónicamente cálida/fría usando el mismo tono a bajo croma, (3) asignaciones de tokens semánticos, y (4) compañeros de acento opcionales generados rotando el tono OKLCH.
- La generación de paletas de modo oscuro invierte las asignaciones de función de tono y ajusta el croma: los colores de superficie usan croma reducido para evitar dureza; los colores interactivos (botón, enlace) usan luminosidad aumentada para permanecer vívidos sobre fondos oscuros.
- Bibliotecas como Radix UI Colors y Culori implementan estos algoritmos con calidad de producción. Usa el Generador de tonos y el Generador de paletas para acceso interactivo y sin código a las mismas técnicas.
Colores relacionados
Marcas relacionadas
Herramientas relacionadas
Generador de tonos
Genera escalas de tonos estilo Tailwind CSS (50–950) a partir de cualquier color base para sistemas de diseño.
Generador de paletas
Genera paletas de colores armoniosas usando esquemas complementarios, análogos, triádicos y complementarios divididos.