¿Cómo agregar modo oscuro?

ui

Tiempo de lectura:

Publicado:

Actualizado:

Ver en YouTube

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 usuario
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
// Aplica dicha preferencia
if (prefersDark) {
document.documentElement.dataset.theme = "dark";
}
// Actualiza el tema cuando cambie la preferencia mediante el sistema operativo
prefersDark.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ón
const themeButton = document.querySelector("#theme-button");
// Cambiamos el tema cuando se haga clic en el botón
themeButton.addEventListener("click", (e) => {
if (document.documentElement.dataset.theme === "light") {
document.documentElement.dataset.theme = "dark";
} else {
document.documentElement.dataset.theme = "light";
}
});

Visualización

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;
}

Visualización

Y cambiar cualquier otro elemento utilizando los selectores necesarios:

button {
background-color: tomato;
color: white;
}
[data-theme="dark"] button {
background-color: slateblue;
color: white;
}

Visualización

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 selector
const themeSelector = document.querySelector("#theme-selector");
// Cambia el atributo de tema cuando cambie la opción
themeSelector.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 actual
for (const option of themeSelector.options) {
if (option.value === document.documentElement.dataset.theme) {
option.selected = true;
}
}
Visualización

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
<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>

JavaScript
const userTheme = localStorage.getItem("theme");
// Consigue la preferencia del usuario
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
// Aplica dicha preferencia
if (!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 operativo
prefersDark.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 correcta
for (const option of themeSelector.options) {
if (option.value === document.documentElement.dataset.theme) {
option.selected = true;
}
}
// Cambia el atributo de tema cuando cambie la opción
themeSelector.addEventListener("change", (e) => {
const selectedTheme = e.target.value;
localStorage.setItem("theme", selectedTheme);
document.documentElement.dataset.theme = selectedTheme;
});

CSS
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;
}

Comentarios