Hace poco, me propuse migrar mi web personal a otra tecnología frontend por diferentes razones. La primera versión que hice me permitió aprender bastantes cosas, pero hubo otras, como tener temas dark
y light
en mi web que no introduje porque no quería meter dependencias en mi proyecto de blog. En su momento, me parecía que podía haber mucha duplicidad de selectores y tenía que manejar estados (por estar sesgado a React) pero ahora, tras leer y aprovechar mejor las capacidades de CSS y tirando lo mínimo de JS (solo para mejorar la experiencia de desarrollo y usuario), veo que es bastante simple.
No voy a explicar en detalle este aspecto, para ello es mejor ir a una buena documentación como:
En pocas palabras, se trata de declarar variables en la pseudo-clase :root
, y así aplicarlas globalmente al documento HTML. Es decir, podemos acceder a estas en cualquier otra regla simplemente usando la función var([--tu-variable])
, permitiéndose así la reutilización.
calc
Esta función nos permitirá generar dinámicamente valores y, junto a las variables, persistirlas para poder ser reutilizadas. Como antes, no iré más allá, pero te dejo buena documentación de las mismas fuentes por si necesitas algún detalle adicional.
Este es un aspecto interesante para ahorrarse reglas de CSS y tener menos especificidad en la hoja de estilos. Por defecto, hay propiedades que de padres a hijos, se heredan, como el color, pero hay otras, como el border, que no. Este comportamiento puede alterarse, pero en nuestro caso en específico, el comportamiento por defecto nos favorece. De este modo, podemos usar el elemento padre para especificar nuestro tema de forma general y, en elementos específicos dar otros colores porque así se decidió en diseño. ¿Qué estilos podemos ahorrarnos así? Pues el color del texto, los fondos de los elementos, etc.
¿Quieres saber más sobre este tema? Te dejo la siguiente documentación (sí, de las mismas fuentes de siempre):
light-dark()
Esta es una nueva funcionalidad que me encontré en CSS, al momento de escribir esto, no es totalmente compatible con Safari, pero si con el resto de navegadores (échale un ojo a su compatibilidad en el enlace que te compartiré a continuación). Está función está muy bien explicada en la siguiente documentación :
En pocas palabras, se trata de pasarle la definición del color para el tema light primero y luego el tema dark ¿Cómo se van a aplicar? ¿Cómo se elegirá uno u otro? ¿Cómo sabemos que estamos en un tema u otro? Tenemos que declarar que ese elemento usará un schema concreto con otra propiedad, la cual a su vez debemos definir los schemas que tendrán nuestros estilos disponibles y seleccionar el valor de una de estas. “¡Ey Ey! ¿No era que esto evitaría duplicidades y cualquier cosa que hiciera un infierno el mantenimiento?” Seguro que estás pensando eso ahora mismo, y es normal. Pero vamos a trazar una estrategia, y verás que quedará bastante simple.
Ya tenemos los elementos necesarios de CSS para ponernos manos a la obra. Vamos a crear dos páginas HTML que representarán parte de un Blog de un programador (sin mucha floritura). Queremos que tenga la funcionalidad de poder navegar entre sí y que el usuario que la visite pueda usar o el tema Dark o el tema Light con simplemente un botón. Este además debería dar feedback de que hemos cambiado de tema y en cuál estamos de una manera sencilla. Así dado el contexto, veamos el código. Por cierto, si quieres ver todo el código, está en este repositorio: Ejemplo Simple
Para no estar llenando esto de código, solo veremos lo relevante con una de ellas. Lo importante, la estructura, que importemos el global.css y pongamos el script al final del body para que se ejecute una vez todos los elementos estén renderizados.
<html lang="en">
<head>
<!-- mas tags -->
<link rel="stylesheet" href="global.css">
</head>
<body>
<header>
<h1>My Blog</h1>
<a href="about.html" >About</a>
<button id="theme-toggle">🌞</button>
</header>
<main>
<article>
<h2>Blog Post Title 1</h2>
<p>Blog post summary...</p>
<a href="#">Read More</a>
</article>
<!-- mas artículos... -->
</main>
<footer> <p>© 2024 My Blog</p> </footer>
<script src="script.js"></script>
</body>
</html>
Por otro lado, a nivel más descriptivo tenemos:
El archivo index.html es la página de inicio del blog. Contiene una lista de publicaciones de blog, cada una con un título y un resumen. Cada publicación tiene un enlace “Read More” que, presumiblemente, llevaría a una página con la publicación completa, (pero no).
El archivo about.html es una página de información sobre el desarrollador del blog. Contiene información sobre el desarrollador, una lista de sus habilidades y detalles de contacto (inventadas evidentemente).
Aquí está casi todo el jugo. Veremos que lo primero que haremos accesible a todos los elementos la declaración de color-schema
que pueden ser más pero en nuestro caso queremos las definidas. Luego, declaramos una serie de variables que nos permitirán definir al menos el color primario y secundario (puedes crear más colores con estas para definir una paleta más completa). Estas variables sumando y restando al hue nos darán los colores de la paleta, como vemos, usando la función calc
. Observarás que estamos usando lch para definir el color apartir de hue. Esto puede ser algo largo de explicar y no quiero irme por las ramas, te explico brevemente que es y te dejo enlaces si quieres jugar más allá del ejemplo. Bueno aquí va:
Así por lo menos podemos entender que con esto accedemos a más colores y que tenemos aspectos como luminosidad que nos darán juego para definir los temas dark y light. Lo dicho, te dejo enlaces para indagar más:
Por último, la manera de resolver la duda de especificar el color-scheme en los elementos se puede resolver con el uso clases y, dentro de estos, que se aplique está única regla de forma alineada al nombre de la clase como vemos en el código CSS:
:root {
color-scheme: light dark;
--complimentary: 180;
--split-complimentary: 150;
--triadic: 120;
--analogous: 30;
--hue: 250; /* CENTRO DEL CÁLCULO */
--secondary-hue: calc(var(--hue) + var(--complimentary));
--color-primary: light-dark(lch(55% 80 var(--hue)), lch(20% 20 var(--hue)));
--color-secondary: light-dark(lch(55% 80 var(--secondary-hue)), lch(20% 20 var(--secondary-hue)));
--color: light-dark(lch(20% 0 var(--hue)), lch(98% 0 var(--hue)));
--color--lighter: light-dark(lch(40% 0 var(--hue)), lch(65% 0 var(--hue)));
--color-bg: light-dark(lch(98% 0 var(--hue)), lch(20% 0 var(--hue)));
}
.light {
color-scheme: light;
}
.dark {
color-scheme: dark;
}
Ahora veremos el pequeño script.js que nos permitirá inyectar y cambiar estas clases en todos los elementos html del documento.
Este script contiene una función initializeTheme()
que se encarga de cambiar el tema del sitio web entre claro y oscuro. Cuando el usuario hace clic en el botón de alternancia de tema (representado por un emoji de sol o luna), la función cambia el tema y guarda la preferencia del usuario en el almacenamiento local del navegador. Cuando la página se carga, la función aplica el tema guardado en el almacenamiento local, o utiliza el esquema de color preferido del sistema operativo del usuario si no se ha guardado ninguna preferencia.
function initializeTheme() {
document.getElementById("theme-toggle").addEventListener("click", () => {
const isDark = document.documentElement.classList.toggle("dark");
document.getElementById("theme-toggle").textContent = isDark ? "🌜": "🌞";
localStorage.setItem("theme", isDark ? "dark" : "light");
});
const currentTheme = () => localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
// Apply the theme from local storage when the page loads
document.documentElement.classList.add(currentTheme());
}
initializeTheme();
Vamos a explicar un poco más en detalle este script:
La función initializeTheme()
se ejecuta cuando se carga la página. Esta función agrega un evento de clic al botón con el ID "theme-toggle"
. Este botón es el que el usuario puede hacer clic para cambiar entre los temas claro y oscuro.
Cuando se hace clic en el botón, se ejecuta una función que alterna la clase "dark"
en el elemento document.documentElement
(que representa el elemento <html>
en el DOM). Si la clase "dark"
está presente, se elimina, y si no está presente, se agrega.
Dependiendo de si la clase "dark"
está presente o no después de la alternancia, se cambia el contenido del botón a “🌜” o “🌞” y se guarda la preferencia del tema en el almacenamiento local del navegador.
La función currentTheme()
se utiliza para determinar el tema actual. Primero, intenta obtener el tema del almacenamiento local. Si no hay nada en el almacenamiento local, entonces verifica la preferencia de esquema de color del sistema operativo del usuario utilizando window.matchMedia('(prefers-color-scheme: dark)').matches
. Si el esquema de color preferido es oscuro, devuelve 'dark'
, de lo contrario devuelve 'light'
.
Al final de la función initializeTheme()
, se aplica el tema actual al cargar la página. Esto se hace agregando la clase devuelta por currentTheme()
al document.documentElement.
En el archivo colors.css
, los selectores .light
y .dark
cambian la propiedad color-scheme
, lo que afecta a todo el documento. Por lo tanto, cuando se cambia el tema, todos los elementos en el documento heredan el nuevo valor de color-scheme
, lo que permite cambiar los colores de todo el sitio web.
La propiedad color-scheme
en CSS se aplica al elemento en el que se declara y a todos sus descendientes. En este caso, se declara en el elemento :root
, que representa el elemento raíz del documento, generalmente el elemento <html>
. Por lo tanto, todos los elementos en el documento heredarán la propiedad color-scheme.
Cómo podemos ver, con una serie de conceptos, que seguramente ya uses para otros casos, podemos tener temas dark y light en nuestra web sin tener dependencias externas. El ejemplo es muy plano y puede ser optimizado usando librerías como react o frameworks como Astro para encapsular mejor ciertas cosas como en un ThemeIcon
.
¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.
Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?
Pero espera 🖐 que tenemos un conflicto interno. A nosotros las newsletter nos parecen 💩👎👹 Por eso hemos creado la LEAN LISTA, la primera lista zen, disfrutona y que suena a rock y reggaeton del sector de la programación. Todos hemos recibido newsletters por encima de nuestras posibilidades 😅 por eso este es el compromiso de la Lean Lista