CSS color-mix() y colores relativos: tintes, hover y transparencia
color-mix() mezcla dos colores en el espacio de color que elijas, y la sintaxis de color relativo te permite editar un canal de un color existente, como en oklch(from var(--brand) calc(l - 0.1) c h). Las dos funcionan en el navegador, aceptan variables CSS y sustituyen a la mayoría de las funciones de color de Sass. Mezcla en oklab para tintes y hover, y en srgb para transparencias.
Lectura: 7 min
Cómo funciona color-mix()
color-mix() recibe un espacio de color y dos colores con porcentajes opcionales, y devuelve el color intermedio. color-mix(in oklab, var(--brand) 90%, black) significa 90 % de marca y 10 % de negro, mezclados en Oklab. Con un solo porcentaje, el otro color se queda con el resto. Sin ninguno, cada uno aporta el 50 %.
El navegador calcula el resultado en tiempo de ejecución, así que funciona con propiedades personalizadas, currentColor y cambios de tema. Las funciones de Sass se ejecutan una vez al compilar y no ven los valores de var().
Si los dos porcentajes suman menos del 100 %, el resultado también se vuelve parcialmente transparente: color-mix(in srgb, red 30%, blue 30%) es una mezcla a partes iguales con un 60 % de opacidad. Mantén la suma en 100 % salvo que quieras ese efecto.
CSS
.button {
--brand: #2563eb;
background: var(--brand);
}
.button:hover {
background: color-mix(in oklab, var(--brand) 90%, black);
}
.badge {
background: color-mix(in oklab, var(--brand) 10%, white);
border-color: color-mix(in srgb, var(--brand) 30%, transparent);
}En qué espacio de color mezclar: srgb, oklab u oklch
El espacio cambia el resultado más de lo que la mayoría espera. Abajo tienes el azul puro #0000ff mezclado al 50/50 con el amarillo puro #ffff00, calculado con la misma fórmula que usa el navegador.
En srgb los canales se promedian exactamente en 128, 128, 128: la mezcla es #808080, un gris con croma cero. Oklab también mezcla en línea recta, y el azul y el amarillo quedan en lados opuestos, así que casi se anulan en un azul grisáceo apagado. Oklch, en cambio, interpola el ángulo de tono por el camino corto de 264° a 110°, así que el punto medio es un verde azulado vivo con tono 187° que conserva el croma de los dos colores.
- Azul #0000ff#0000ff
- Amarillo #ffff00#ffff00
- en srgb: #808080#808080
- en oklab: #6cabc7#6cabc7
- en oklch: #00bdb0#00bdb0
| Espacio | Azul + amarillo | OKLCH L, C | Blue-600 + yellow-400 | OKLCH L, C |
|---|---|---|---|---|
| srgb | #808080 | 0.600, 0 | #909880 | 0.665, 0.035 |
| oklab | #6cabc7 | 0.710, 0.077 | #90a3ad | 0.703, 0.026 |
| oklch | #00bdb0 | 0.710, 0.262 | #00bd9e | 0.703, 0.194 |
La segunda pareja son #2563eb y #facc15 de Tailwind. Los resultados en oklch quedan fuera de sRGB, así que el croma se muestra antes del ajuste de gama y el hex es el color más cercano que puede mostrar una pantalla sRGB.
Destacan dos cosas. Srgb se queda en una luminosidad de 0,600, mientras que el punto medio perceptual entre el azul (0,452) y el amarillo (0,968) es 0,710: ese es el centro oscuro y turbio de los degradados en srgb. Y solo oklch mantiene viva la mezcla, a cambio de pasar por un tono que no tiene ninguno de los dos colores.
Regla práctica: mezcla en oklab para tintes, sombras y estados hover, donde te quedas en un mismo tono y quieres pasos de luminosidad uniformes. Mezcla en oklch cuando los dos colores son saturados y quieres un resultado saturado. Mezcla en srgb para transparencias, porque coincide con cómo el navegador compone las capas.
Recetas: hover, tintes y variantes transparentes
Todos los valores de abajo usan #2563eb como --brand. El texto blanco sobre él tiene una relación de contraste de 5,17:1.
Mezclar un 10 % de negro oscurece más en oklab que en srgb: el texto blanco sube a 6,50:1 frente a 6,11:1. Oklab y oklch dan el mismo resultado cuando el segundo color es negro o blanco, porque estos no tienen tono que interpolar.
| Receta | CSS | Resultado | Contraste |
|---|---|---|---|
| Hover, 10 % más oscuro | color-mix(in oklab, var(--brand) 90%, black) | #1f55cc | 6.50:1 con texto blanco |
| Active, 20 % más oscuro | color-mix(in oklab, var(--brand) 80%, black) | #1947ae | 8.22:1 con texto blanco |
| Hover mezclado en srgb | color-mix(in srgb, var(--brand) 90%, black) | #2159d3 | 6.11:1 con texto blanco |
| Fondo tintado, 10 % | color-mix(in oklab, var(--brand) 10%, white) | #e8f0ff | 4.51:1 con texto de marca |
| Fondo tintado, 20 % | color-mix(in oklab, var(--brand) 20%, white) | #d2e2ff | 3.95:1 con texto de marca |
| Transparente, 20 % | color-mix(in srgb, var(--brand) 20%, transparent) | rgba(37, 99, 235, 0.2) | Se ve como #d3e0fb sobre blanco |
Cuidado con los tintes. El texto de marca se lee a 5,17:1 sobre blanco, a 4,51:1 sobre un tinte del 10 % y solo a 3,95:1 sobre un tinte del 20 %, que no pasa WCAG AA para texto normal. Si pones texto del color de marca sobre un tinte de marca, usa para el texto el tono de hover.
Mezclar con transparent conserva el color y solo baja el alfa, porque el navegador mezcla valores premultiplicados. El resultado se adapta a lo que hay detrás: la misma regla se ve como #d3e0fb sobre blanco y como #132651 sobre una superficie oscura #0f172a. Un tinte mezclado con blanco sigue pálido en una página oscura, así que para componentes que aparecen en los dos temas la versión transparente suele ser la mejor opción.
Sintaxis de color relativo: edita un solo canal
La sintaxis de color relativo toma un color existente, lo separa en canales y te deja reescribir cualquiera de ellos con calc(). En oklch(from var(--brand) l c h), las palabras clave l, c y h contienen la luminosidad (de 0 a 1), el croma y el tono de --brand.
A diferencia de color-mix(), cambia exactamente una propiedad: bajar la luminosidad sin tocar el croma, girar el tono sin cambiar la luminosidad o añadir alfa a un color guardado en hex.
| Receta | CSS | Resultado a partir de #2563eb | Texto blanco |
|---|---|---|---|
| Más oscuro | oklch(from var(--brand) calc(l - 0.1) c h) | #0442c8 | 8.02:1 |
| Más claro | oklch(from var(--brand) calc(l + 0.1) c h) | #4484ff | 3.51:1 |
| Mitad de croma | oklch(from var(--brand) l calc(c * 0.5) h) | #4e6faf | 4.98:1 |
| Tono +30° | oklch(from var(--brand) l c calc(h + 30)) | #7a49df | 5.46:1 |
| Complementario | oklch(from var(--brand) l c calc(h + 180)) | #956700 | 4.98:1 |
| Gris, misma luminosidad | oklch(from var(--brand) l 0 h) | #707070 | 4.95:1 |
| Alfa 50 % | oklch(from var(--brand) l c h / 0.5) | rgba(37, 99, 235, 0.5) | Depende del fondo |
CSS
:root {
--brand: #2563eb;
--brand-hover: oklch(from var(--brand) calc(l - 0.08) c h);
--brand-muted: oklch(from var(--brand) l calc(c * 0.5) h);
--brand-ring: oklch(from var(--brand) l c h / 0.4);
--accent: oklch(from var(--brand) l c calc(h + 30));
}Fíjate en las cuatro filas que conservan l: cambiar el croma o el tono mueve el contraste con texto blanco solo entre 4,95:1 y 5,46:1, frente a 5,17:1 del original. La luminosidad es lo que decide el contraste, así que las variantes de tono creadas así mantienen más o menos la misma accesibilidad. Los colores más claro y complementario quedan fuera de sRGB con el croma completo, y el navegador los ajusta al color más cercano que puede mostrar.
color-mix() frente a colores relativos y darken() de Sass
Usa color-mix() cuando pienses en «un poco de negro» o «un poco de transparencia». Usa la sintaxis de color relativo cuando pienses en canales: «el mismo color, 0,1 más oscuro». Las dos sustituyen a la mayoría de las funciones de color de Sass y siguen los cambios de tema sin recompilar.
| color-mix() | Sintaxis de color relativo | darken() de Sass | |
|---|---|---|---|
| Se ejecuta | En el navegador | En el navegador | Al compilar |
| Funciona con var() y currentColor | Sí | Sí | No |
| Modelo de color | El espacio que indiques: srgb, oklab, oklch, hsl y más | Cualquier función de color: oklch(), rgb(), hsl(), lab() | Solo HSL |
| Ideal para | Tintes, sombras, transparencia, mezclar dos tokens | Cambiar un canal: luminosidad, croma, tono o alfa | Código antiguo con colores fijos |
| Tamaño del paso | Una fracción de la distancia al segundo color | Exacto, en las unidades del canal | Puntos HSL fijos, desiguales a la vista |
| Soportado desde | Chrome 111, Safari 16.2, Firefox 113 | Chrome 119, Safari 18, Firefox 128 | Cualquier navegador, la salida es estática |
Por qué darken() de Sass da pasos desiguales
darken($color, 10%) resta 10 puntos de luminosidad HSL. La luminosidad HSL no es la luminosidad percibida, así que el cambio visible depende del tono. Aquí tienes darken(10%) junto a oklch(from … calc(l - 0.1) c h) en cuatro colores habituales de interfaz, medido con ΔE OK, donde unos 0,02 son la diferencia más pequeña que puedes ver.
| Color | Sass darken(10%) | ΔE OK | OKLCH l − 0.1 | ΔE OK |
|---|---|---|---|---|
| #2563eb | #134cca | 0.078 | #0442c8 | 0.099 |
| #facc15 | #d7ad05 | 0.099 | #d9ab00 | 0.101 |
| #16a34a | #107636 | 0.136 | #00842b | 0.094 |
| #dc2626 | #b21d1d | 0.090 | #b70000 | 0.089 |
El mismo darken(10%) cambia el verde 1,7 veces más que el azul. La versión OKLCH se mantiene entre 0,089 y 0,101 en todos los tonos; la pequeña variación viene del ajuste de gama, ya que algunos colores más oscuros quedan justo fuera de sRGB. El propio Sass recomienda color.adjust() o color.scale() en lugar de darken(), y en CSS puro no necesitas ninguno de los dos.
Soporte en navegadores y alternativas
color-mix() funciona en Chrome y Edge 111, Safari 16.2 y Firefox 113, todos publicados en 2023. La sintaxis de color relativo llegó en Chrome y Edge 119 y en Firefox 128; Safari incluyó una versión temprana en la 16.4 y la sintaxis actual en Safari 18. Todos los navegadores actuales soportan las dos.
El truco habitual de poner un valor simple en la línea anterior no funciona cuando el valor nuevo contiene var(). Una declaración con var() siempre se acepta al analizar el CSS, así que el navegador descarta tu línea de respaldo y, si no puede calcular color-mix(), recurre al valor heredado o inicial. Envuelve la versión moderna en @supports.
CSS
.button:hover {
background: #1f55cc;
}
@supports (color: color-mix(in oklab, red, blue)) {
.button:hover {
background: color-mix(in oklab, var(--brand) 90%, black);
}
}
@supports (color: oklch(from red l c h)) {
.badge {
border-color: oklch(from var(--brand) l c h / 0.3);
}
}Preguntas y respuestas
¿Qué hace color-mix() en CSS?
color-mix() mezcla dos colores en el espacio de color que indiques y devuelve el resultado, por ejemplo color-mix(in oklab, var(--brand) 90%, black) para un color un 10 % más oscuro que el de marca. Se ejecuta en el navegador, así que funciona con variables CSS y currentColor.
¿Qué espacio de color debo usar en color-mix()?
Usa oklab para tintes, sombras y estados hover, porque da pasos de luminosidad uniformes y conserva el tono. Usa oklch cuando mezcles dos colores saturados y quieras un resultado saturado, y srgb cuando mezcles con transparent. Azul y amarillo mezclados en srgb dan un gris plano, #808080, mientras que oklch da un verde azulado vivo.
¿Cómo oscurezco un color en hover con CSS puro?
Usa background: color-mix(in oklab, var(--brand) 90%, black) o background: oklch(from var(--brand) calc(l - 0.1) c h). Para #2563eb, la primera da #1f55cc y la segunda #0442c8, y el contraste con texto blanco sube de 5,17:1 a 6,50:1 y 8,02:1.
¿Cómo hago transparente el color de una variable CSS?
Escribe color-mix(in srgb, var(--brand) 20%, transparent) u oklch(from var(--brand) l c h / 0.2). Las dos funcionan aunque --brand esté guardada en hex, y dejan el color igual, bajando solo su opacidad.
¿color-mix() funciona en todos los navegadores?
Sí, todos los navegadores actuales lo soportan: Chrome y Edge desde la 111, Safari desde la 16.2 y Firefox desde la 113. Para navegadores antiguos, pon primero un color estático y la versión con color-mix() dentro de @supports, porque una línea con var() no puede recurrir sola a un valor de respaldo.
¿Qué diferencia hay entre color-mix() y la sintaxis de color relativo?
color-mix() mezcla dos colores según un porcentaje, lo que va bien para tintes, sombras y transparencia. La sintaxis de color relativo, como oklch(from var(--brand) l c calc(h + 30)), reescribe canales concretos de un color, lo que va bien para pasos exactos de luminosidad, cambios de tono y alfa.