Un enfoque SECO para los temas de color en CSS

El otro día, Florens Verschelde preguntó sobre la definición de estilos de modo oscuro tanto para una clase como para una consulta de medios, sin repetir declaraciones de propiedades personalizadas de CSS. Me había encontrado con este problema en el pasado pero no encontré una solución adecuada.

Lo que queremos es evitar redefinir (y, por tanto, repetir) las propiedades personalizadas al cambiar entre los modos claro y oscuro. Ese es el objetivo de la programación DRY (Don’t Repite Yourself), pero el patrón típico para cambiar de tema suele ser algo como esto:

:root {  --background: #fff;  --text-color: #0f1031;  /* etc. */}@media (prefers-color-scheme: dark) {  :root {    --background: #0f1031;    --text-color: #fff;    /* etc. */  }}

¿Ves lo que quiero decir? Claro, puede que no parezca gran cosa en un ejemplo abreviado como este, pero imagina hacer malabarismos con docenas de propiedades personalizadas a la vez: ¡eso es mucha duplicación!

Entonces recordé el truco de Lea Verou usando --var: ;, y aunque al principio no me di cuenta, encontré una manera de hacerlo funcionar: no con var(--light-value, var(--dark-value))una combinación anidada como esa, ¡sino usando ambos uno al lado del otro!

Ciertamente, alguien más inteligente debe haber descubierto esto antes que yo, pero no he oído hablar de aprovechar (o más bien abusar ) de las propiedades personalizadas de CSS para lograrlo. Sin más preámbulos, aquí está la idea:

--color: var(--light, orchid) var(--dark, rebeccapurple);

Si el --lightvalor se establece en initial, se utilizará el respaldo ( orchid), lo que significa --darkque se debe establecer en un carácter de espacio en blanco (que es un valor válido), lo que hace que el valor calculado final se vea así:

--color: orchid  ; /* Note the additional whitespace */

Por el contrario, si --lightse establece en un espacio en blanco y --darken initial, terminamos con un valor calculado de:

--color:   rebeccapurple; /* Again, note the whitespace */

Ahora, esto es genial, pero necesitamos definir las propiedades personalizadas --lighty --dark, según el contexto. El usuario puede tener una preferencia del sistema establecida (ya sea clara u oscura), o puede haber alternado el tema del sitio web con algún elemento de la interfaz de usuario. Al igual que en el ejemplo de Florens, definiremos estos tres casos, con algunas mejoras menores en la legibilidad que Lea propuso usando constantes “encendido” y “apagado” para que sea más fácil de entender de un vistazo:

:root {   /* Thanks Lea Verou! */  --ON: initial;  --OFF: ;}/* Light theme is on by default */.theme-default,.theme-light {  --light: var(--ON);  --dark: var(--OFF);}/* Dark theme is off by default */.theme-dark {  --light: var(--OFF);  --dark: var(--ON);}/* If user prefers dark, then that's what they'll get */@media (prefers-color-scheme: dark) {  .theme-default {    --light: var(--OFF);    --dark: var(--ON);  }}

Luego podemos configurar todas nuestras variables de tema en una sola declaración, sin repeticiones. En este ejemplo, las theme-*clases están configuradas para el htmlelemento, por lo que podemos usarlas :rootcomo selector, como a mucha gente le gusta hacer, pero puedes configurarlas en body, si la naturaleza en cascada de las propiedades personalizadas tiene más sentido de esa manera:

:root {  --text: var(--light, black) var(--dark, white);  --bg: var(--light, orchid) var(--dark, rebeccapurple);}

Y para usarlos, los usamos var()con respaldos incorporados, porque nos gusta ser cuidadosos:

body {  color: var(--text, navy);  background-color: var(--bg, lightgray);}

Esperemos que ya estés empezando a ver los beneficios aquí. En lugar de definir y cambiar un montón de propiedades personalizadas, trataremos con dos y configuraremos todas las demás solo una vez :root. Esa es una gran mejora desde donde empezamos.

Aún más SECADORA con preprocesadores

Si me mostraras la siguiente línea de código fuera de contexto, ciertamente estaría confundido porque un color es un valor único, ¡no dos!

--text: var(--light, black) var(--dark, white);

Por eso prefiero abstraer un poco las cosas. Podemos configurar una función con nuestro preprocesador favorito, que en mi caso es Sass. Si mantenemos nuestro código arriba definiendo nuestros valores --lighty --darken varios contextos, necesitamos realizar un cambio solo en la declaración de propiedad personalizada real. Creemos una light-darkfunción que nos devuelva la sintaxis CSS:

@function light-dark($light, $dark) {  @return var(--light, #{ $light }) var(--dark, #{ $dark });}

Y lo usaríamos así:

:root {   --text: #{ light-dark(black, white) };   --bg: #{ light-dark(orchid, rebeccapurple) };   --accent: #{ light-dark(#6d386b, #b399cc) };}

Notarás que hay delimitadores de interpolación #{ … }alrededor de la llamada a la función. Sin estos, Sass generaría el código tal como está (como una función CSS básica). Puedes jugar con varias implementaciones de esto, pero la complejidad de la sintaxis depende de tus gustos.

¿Qué te parece eso de una base de código mucho más SECA?

¿Más de un tema? ¡Ningún problema!

Potencialmente podrías hacer esto con más de dos modos. Cuantos más temas agregues, más complejo será gestionarlo, ¡pero la cuestión es que es posible ! Agregamos otro conjunto de temas ONo OFFvariables y configuramos una variable adicional en la lista de valores.

.theme-pride {  --light: var(--OFF);  --dark: var(--OFF);  --pride: var(--ON);}:root {  --text:    var(--light, black)    var(--dark, white)    var(--pride, #ff8c00)  ; /* Line breaks are absolutely valid */  /* Other variables to declare… */}

¿Es esto un truco? Sí, absolutamente lo es. ¿Es este un gran caso de uso para posibles booleanos CSS que aún no existen? Bueno, ese es el sueño.

¿Y tú? ¿Es esto algo que has descubierto con un enfoque diferente? Compártelo en los comentarios!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir