Saltar al contenido

CSS, de a poco

Curso de CSS

Color moderno

Lección 34 de 37 · 4 min

Con oklch, color-mix y variables ya podés armar una paleta. CSS ahora suma funciones que derivan el resto del tema a partir de un solo color, y eligen solas entre claro y oscuro.

Un valor para los dos temas

light-dark() recibe dos colores: devuelve el primero si el elemento usa el esquema claro y el segundo si usa el oscuro. Quién decide es la propiedad color-scheme. Con color-scheme: light dark en :root, el navegador sigue la preferencia del sistema, la misma que lee @media (prefers-color-scheme: dark). Con color-scheme: dark forzás el oscuro en ese elemento y sus hijos. Sin color-scheme, siempre devuelve el claro.

css
:root {
color-scheme: light dark;
}
body {
background: light-dark(oklch(0.97 0.02 250), oklch(0.25 0.03 250));
color: light-dark(oklch(0.25 0.03 250), oklch(0.97 0.02 250));
}

light-dark() es Baseline 2024 (newly available): anda en Chrome, Firefox y Safari actuales, pero no en versiones de antes de 2024. Solo acepta colores: para una imagen distinta en oscuro seguí usando la media query. Además, color-scheme también cambia los colores del navegador: campos de formulario, barras de scroll y fondo de la página.

Elegí un esquema y mové el tono de la marca: la hoja de tokens cambia con light-dark(), y el botón y las cuatro muestras salen de un solo color. Las muestras se ubican en su luminosidad real, y las marcas naranjas muestran cuánto se movió cada una.

color-scheme
un color, cuatro tokens

Tokens de color

Todo sale de --marca: solo cambia el tono.

--marca
oklch(0.6 0.15 250)
--marca-clara
oklch(0.85 0.15 250)
--marca-oscura
oklch(0.35 0.15 250)
--marca-apagada
oklch(0.6 0.05 250)
Tarjeta en esquema claro
tema.css
.tema {
color-scheme: light;
--marca: oklch(0.6 0.15 250);
/* respaldo: valores calculados de antemano */
--marca-clara: oklch(0.85 0.15 250);
--marca-oscura: oklch(0.35 0.15 250);
--marca-apagada: oklch(0.6 0.05 250);
}
@supports (color: oklch(from red l c h)) {
.tema {
--marca-clara: oklch(from var(--marca) calc(l + 0.25) c h);
--marca-oscura: oklch(from var(--marca) calc(l - 0.25) c h);
--marca-apagada: oklch(from var(--marca) l calc(c / 3) h);
}
}
.tarjeta {
background: oklch(0.97 0.02 250);
color: oklch(0.25 0.03 250);
}
@supports (color: light-dark(white, black)) {
.tarjeta {
background: light-dark(oklch(0.97 0.02 250), oklch(0.25 0.03 250));
color: light-dark(oklch(0.25 0.03 250), oklch(0.97 0.02 250));
}
}
.boton {
background: var(--marca);
color: black; /* 5.3:1 */
}
@supports (color: contrast-color(red)) {
.boton {
color: contrast-color(var(--marca));
}
}
tema.html
<div class="scheme-light [--marca:oklch(0.6_0.15_250)]">
<div class="bg-[light-dark(oklch(0.97_0.02_250),oklch(0.25_0.03_250))] text-[light-dark(oklch(0.25_0.03_250),oklch(0.97_0.02_250))] rounded-lg p-4">
<h3>Tokens de color</h3>
<div class="bg-[oklch(from_var(--marca)_calc(l_+_0.25)_c_h)] p-3"></div>
<div class="bg-[oklch(from_var(--marca)_calc(l_-_0.25)_c_h)] p-3"></div>
<div class="bg-[oklch(from_var(--marca)_l_calc(c_/_3)_h)] p-3"></div>
<button class="bg-(--marca) text-black supports-[color:contrast-color(red)]:text-[color:contrast-color(var(--marca))] rounded-md px-3 py-1.5">Guardar tema</button>
</div>
</div>

Colores relativos

La sintaxis de color relativo (oklch(from …)) toma un color de origen y lo separa en sus canales: l, c y h. Cada canal es un número que podés dejar igual o cambiar con calc(). Con --marca: oklch(0.6 0.15 250):

  1. Claracalc(l + 0.25)oklch(from var(--marca) calc(l + 0.25) c h) sube la luminosidad a 0.85.
  2. Oscuracalc(l - 0.25)La baja a 0.35.
  3. Apagadacalc(c / 3)Deja el croma en 0.05, casi gris.
  4. Transparentel c h / 0.2oklch(from var(--marca) l c h / 0.2) agrega 20% de opacidad.
Cinco muestras sacadas de --marca, oklch(0.6 0.15 250): la marca; la clara, con la luminosidad en 0.85; la oscura, en 0.35; la apagada, con el croma en 0.05; y la transparente, al 20% de opacidad sobre una foto.

Si cambiás --marca, cambia todo el tema. Los colores relativos son Baseline 2024 (newly available). Para los navegadores que no los soportan, declará antes los valores ya calculados y poné los relativos dentro de @supports:

css
.tema {
--marca-clara: oklch(0.85 0.15 250);
}
@supports (color: oklch(from red l c h)) {
.tema {
--marca-clara: oklch(from var(--marca) calc(l + 0.25) c h);
}
}

Contraste automático y gama amplia

contrast-color(var(--marca)) devuelve blanco o negro, el que más contraste tenga con ese color. Es Baseline 2026 (newly available), así que usalo como mejora progresiva: un color de texto fijo afuera y contrast-color() dentro de @supports, como en la demo.

Tiene límites: solo elige entre blanco y negro, y el que elige no siempre alcanza el contraste que necesitás. Con luminosidad 0.6 gana el negro en todos los tonos, con entre 4.9:1 y 6.1:1 según WCAG 2: alcanza AA (4.5:1) para texto normal, pero no AAA (7:1).

contrast-color() sobre la marca con tres tonos: tono 30°, black, 5.0:1; tono 140°, black, 5.6:1; tono 320°, black, 4.9:1. Los tres alcanzan AA pero no AAA.

Revisá siempre el resultado con una herramienta de contraste. Para colores más saturados que sRGB, color(display-p3 …) pide colores de la gama P3 de las pantallas modernas. Tanto color() como la media query color-gamut son Baseline widely available. Si la pantalla no llega a P3, el navegador acerca el color al más parecido; con la media query elegís vos el respaldo:

css
.boton {
background: rgb(255 0 128);
}
@media (color-gamut: p3) {
.boton {
background: color(display-p3 1 0 0.5);
}
}

En Tailwind

Tailwind v4 trae utilidades para color-scheme: scheme-light, scheme-dark y scheme-light-dark. Para light-dark(), los colores relativos y contrast-color() no hay utilidad propia: van como valores arbitrarios, con guiones bajos en lugar de espacios.

CSSTailwind
color-scheme: light darkscheme-light-dark
--marca: oklch(0.6 0.15 250)[--marca:oklch(0.6_0.15_250)]
background-color: var(--marca)bg-(--marca)
background-color: light-dark(white, black)bg-[light-dark(white,black)]
oklch(from var(--marca) calc(l + 0.25) c h)bg-[oklch(from_var(--marca)_calc(l+0.25)_c_h)]
@supportssupports-[…]:
boton.html
<div class="scheme-light-dark [--marca:oklch(0.6_0.15_250)]">
<button class="bg-(--marca) text-black
supports-[color:contrast-color(red)]:text-[color:contrast-color(var(--marca))]">
Guardar
</button>
</div>