¿Cómo agregar modo oscuro?
La manera más sencilla de agregar modo oscuro a nuestro sitio web es por medio de un atributo extra en el elemento <html>
.
El primer paso es agregarlo con el valor del tema por defecto:
<html data-theme="light"> <!-- ... --></html>
Alternativa
Alternativamente podemos usar una clase:
<html class="light"> <!-- ... --></html>
Debemos tener en cuenta que los scripts deben cambiar respectivamente para manipular clases en lugar de data-attributes
.
Esto nos ayudará a tener un lugar para guardar el tema actual y poder cambiar su valor mediante JavaScript.
Sobre las preferencias del usuario
Hoy en día podemos obtener el tema preferido del usuario mediante una media query llamada prefers-color-scheme
:
// Consigue la preferencia del usuarioconst prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
// Aplica dicha preferenciaif (prefersDark) { document.documentElement.dataset.theme = "dark";}
// Actualiza el tema cuando cambie la preferencia mediante el sistema operativoprefersDark.addEventListener("change", (e) => { if (e.matches) { document.documentElement.dataset.theme = "dark"; } else { document.documentElement.dataset.theme = "light"; }});
De esta podemos conseguir el valor inicial y hacer el cambio correspondiente si el usuario cambia el tema mediante su sistema operativo.
Sobre la interfaz de usuario
También debemos agregar una manera de que el usuario pueda elegir dentro de la página. Para ello vamos a agregar un botón y un script:
<button id="theme-button">Cambiar tema</button>
// Obtenemos el botónconst themeButton = document.querySelector("#theme-button");
// Cambiamos el tema cuando se haga clic en el botónthemeButton.addEventListener("click", (e) => { if (document.documentElement.dataset.theme === "light") { document.documentElement.dataset.theme = "dark"; } else { document.documentElement.dataset.theme = "light"; }});
Ahora que tenemos la funcionalidad básica podemos agregar los estilos:
html { background-color: beige; color: darkslateblue;}
[data-theme="dark"] { background-color: #3e3e3e; color: #fafafa;}
Y cambiar cualquier otro elemento utilizando los selectores necesarios:
button { background-color: tomato; color: white;}
[data-theme="dark"] button { background-color: slateblue; color: white;}
Sobre la extensibilidad
De esta manera podemos crear diferentes temas. Por ejemplo, si queremos agregar un tema llamado dark-blue
sólo tenemos que agregar los estilos correspondientes:
/* ... */
[data-theme="dark-blue"] { background-color: #002244; color: #fafafa;}
Y crear la interfaz de usuario necesaria. Si es que tenemos más de dos temas lo mejor sería usar un <select>
en lugar de un <button>
:
<select id="theme-selector"> <option value="light">Claro</option> <option value="dark">Oscuro</option> <option value="dark-blue">Azul oscuro</option></select>
// Obtenemos el selectorconst themeSelector = document.querySelector("#theme-selector");
// Cambia el atributo de tema cuando cambie la opciónthemeSelector.addEventListener("change", (e) => { document.documentElement.dataset.theme = e.target.value;});
También tenemos que asegurarnos de que al cargar la página la opción por defecto sea correcta. Podemos hacerlo de la siguiente manera:
// Itera sobre las opciones y le da el atributo selected al que coincida con el tema actualfor (const option of themeSelector.options) { if (option.value === document.documentElement.dataset.theme) { option.selected = true; }}
Sobre la preservación del tema
Todavía nos falta manejar algunos casos:
- Cuando el usuario tiene un tema en nuestra página distinto al de su navegador/sistema operativo.
- Cuando hay más de dos temas disponibles y el usuario elige uno diferente al claro u oscuro.
Para ello podemos guardar la preferencia del usuario en localStorage
:
themeSelector.addEventListener("change", (e) => { const selectedTheme = e.target.value; localStorage.setItem("theme", selectedTheme); document.documentElement.dataset.theme = selectedTheme;});
Y cargarla donde sea necesario:
const userTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
if (!userTheme && prefersDark.matches) { document.documentElement.dataset.theme = "dark";}
if (userTheme) { document.documentElement.dataset.theme = userTheme;}
// ...
Código final
El código final se vería algo así:
<html data-theme="light"> <!-- ... --> <body> <select id="theme-selector"> <option value="light">Claro</option> <option value="dark">Oscuro</option> <option value="dark-blue">Azul oscuro</option> </select> </body></html>
const userTheme = localStorage.getItem("theme");
// Consigue la preferencia del usuarioconst prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
// Aplica dicha preferenciaif (!userTheme && prefersDark.matches) { document.documentElement.dataset.theme = "dark";}
if (userTheme) { document.documentElement.dataset.theme = userTheme;}
// Actualiza el tema cuando cambie la preferencia mediante el sistema operativoprefersDark.addEventListener("change", (e) => { if (e.matches) { document.documentElement.dataset.theme = "dark"; } else { document.documentElement.dataset.theme = "light"; }});
const themeSelector = document.querySelector("#theme-selector");
// Se asegura de que la opción por defecto sea correctafor (const option of themeSelector.options) { if (option.value === document.documentElement.dataset.theme) { option.selected = true; }}
// Cambia el atributo de tema cuando cambie la opciónthemeSelector.addEventListener("change", (e) => { const selectedTheme = e.target.value; localStorage.setItem("theme", selectedTheme); document.documentElement.dataset.theme = selectedTheme;});
html { background-color: beige; color: darkslateblue;}
[data-theme="dark"] { background-color: #3e3e3e; color: #fafafa;}
button { background-color: tomato; color: white;}
[data-theme="dark"] button { background-color: darkblue; color: antiquewhite;}
[data-theme="dark-blue"] { background-color: #002244; color: #fafafa;}