Contenido:

Dark Mode: Buenas Prácticas para Crear un Modo Oscuro Efectivo

En los últimos años, el modo oscuro se ha convertido en un estándar dentro del diseño de interfaces web.

Es importante aclarar que esta publicación no se centra en aspectos visuales o teoría del color, sino en la parte técnica —es decir, el código— necesario para implementar un modo oscuro completamente funcional para nuestros usuarios.

Más allá de sus ventajas estéticas, el modo oscuro también se considera una característica de accesibilidad, ya que puede ayudar a reducir la fatiga visual en entornos con poca iluminación e incluso aliviar molestias como migrañas provocadas por el brillo excesivo de las pantallas, especialmente en interfaces de fondo claro.

Origen del modo oscuro

El modo oscuro tiene su origen en los sistemas operativos móviles como Android e iOS.

Inicialmente, se concibió como una característica destinada a reducir el consumo de batería, ya que muchos dispositivos comenzaron a incorporar pantallas OLED o AMOLED. Estas pantallas funcionan de manera que cada píxel se ilumina de forma independiente, y para representar el color negro, los píxeles simplemente no se encienden.

Por esta razón, el modo oscuro tuvo una mayor adopción en los sistemas móviles, y posteriormente los sistemas operativos de escritorio como Windows, macOS y varias distribuciones de Linux también comenzaron a implementarlo.

Pero, ¿qué ocurre con las páginas web?

En los sitios web no existe una forma automática de alternar entre el modo claro y el modo oscuro, al menos no de manera explícita en su diseño o desarrollo.

Si bien existen extensiones de navegador o bibliotecas de JavaScript que permiten cambiar entre estos “temas”, en muchos casos los resultados no son óptimos debido a la forma en que algunos colores se interpretan o combinan, generando contrastes poco agradables o inconsistencias visuales.

Usando Darkmode.js para establecer el modo oscuro - Mobile Usando Darkmode.js para establecer el modo oscuro - Desktop
Usando Darkmode.js para establecer el modo oscuro.

Por ejemplo, en la imagen anterior, al establecer el modo oscuro usando la biblioteca Darkmode.js, los colores se invierten, ya que utiliza la propiedad CSS mix-blend-mode para “aplicar el modo oscuro”.

Podemos mitigar estos efectos secundarios usando la propiedad isolation, aunque al final esto implica más trabajo del necesario.

Gracias a la evolución del estándar de CSS, hoy en día es mucho más sencillo alternar entre el modo claro y el modo oscuro en un sitio web, utilizando la media query prefers-color-scheme.

De esta forma, somos nosotros mismos quienes controlamos el aspecto visual y ofrecemos a nuestros usuarios una mejor experiencia, sin depender de herramientas de terceros.

Formas de activar el modo oscuro

Gracias a la adopción del modo oscuro en los principales sistemas operativos, actualmente existen dos formas de habilitar el modo oscuro en un sitio web:

Activando el modo oscuro mediante el sistema operativo

Gracias a la media query prefers-color-scheme, podemos detectar si la configuración del sistema operativo del usuario —ya sea en dispositivos móviles o de escritorio— tiene activado el modo oscuro.

Esta media query forma parte del estándar Media Queries Level 5 y cuenta con amplio soporte en los navegadores modernos, aunque su funcionamiento también depende de que el sistema operativo admita la preferencia entre modo claro y modo oscuro.

Soporte de la media query prefers-color-scheme - Mobile Soporte de la media query prefers-color-scheme - Desktop
Soporte de la media query prefers-color-scheme.

Puedes consultar los datos más recientes sobre compatibilidad en diferentes navegadores en el siguiente enlace: caniuse.com/prefers-color-scheme

Esta media query acepta dos valores principales:

Por ejemplo:

styles.css
/* Estilos para el modo claro */
:root {
--background: #fffacd;
--foreground: #333;
}
/* Estilos en modo oscuro */
@media (prefers-color-scheme: dark) {
:root {
--background: #3b4252;
--foreground: #eee;
}
}
body {
background: var(--background);
color: var(--foreground);
}

¿Cómo podemos simular este comportamiento en el navegador?

Durante el desarrollo, no resulta práctico tener que activar o desactivar el modo oscuro desde la configuración del sistema operativo. Afortunadamente, en Chrome y en otros navegadores basados en Chromium, es muy fácil simular este comportamiento.

Para hacerlo, debemos acceder a las herramientas para desarrolladores. Puedes hacerlo de las siguientes formas:

Una vez abiertas las DevTools, presiona Ctrl + Shift + P, escribe en el cuadro de búsqueda “Rendering” y selecciona Show Rendering (si tienes la interfaz en inglés).

Buscando la opción Rendering en las DevTools - Mobile Buscando la opción Rendering en las DevTools - Desktop
Buscando la opción Rendering en las DevTools.

Se abrirá un panel adicional.

Desplázate hacia abajo hasta encontrar la opción Emulate CSS media feature prefers-color-scheme, y desde allí podrás elegir entre los modos claro u oscuro para simular y probar tus estilos sin necesidad de cambiar la configuración del sistema operativo.

Emulando la Media Query prefers-color-scheme - Mobile Emulando la Media Query prefers-color-scheme - Desktop
Emulando la Media Query prefers-color-scheme.

Aunque este enfoque es muy útil durante el desarrollo, tiene un inconveniente: no permite al usuario elegir su tema preferido. Por ejemplo, algunos usuarios podrían querer evitar el modo oscuro en ciertos sitios web, y tendrían que modificar la configuración a nivel del sistema operativo para hacerlo. Además, esta técnica no gestiona la persistencia de las preferencias del usuario, por lo que si el modo oscuro se desactiva en el sistema, el sitio también cambiará automáticamente al modo claro.

Activando el modo oscuro con JavaScript

Para ofrecer a los usuarios una forma sencilla de alternar entre el tema claro y el modo oscuro en nuestro sitio web, podemos hacer uso de JavaScript.

En esta sección te explicaré paso a paso cómo crear un botón que permita cambiar (toggle) entre ambos temas y cómo respetar la preferencia del usuario según la configuración establecida en su sistema operativo.

Creando un botón

Existen diversas formas de diseñar un botón para alternar entre el modo claro y el oscuro.

Por motivos de simplicidad, en este ejemplo no añadiremos estilos personalizados al botón, centrándonos únicamente en la funcionalidad.

index.html
<button class="toggle" type="button">Alternar tema</button>

Estilos básicos

Primero, añadiremos los estilos necesarios a nuestra página web para que luzca correctamente tanto en el modo claro como en el modo oscuro.

styles.css
/* Estilos para el modo claro */
:root {
--background: #fffacd;
--foreground: #333;
}
/* Estilos en modo oscuro */
:root.dark {
--background: #3b4252;
--foreground: #eee;
}
body {
background: var(--background);
color: var(--foreground);
/* Transición entre los colores en modo claro y oscuro */
transition:
background,
color 300ms ease;
}

Como habrás notado, se ha omitido el uso de la media query prefers-color-scheme, debido a las razones explicadas en las sección anterior.

Detectando la preferencia del color

Sin embargo, sí podemos utilizar la media query prefers-color-scheme desde JavaScript para detectar la configuración de tema que el usuario tiene en su sistema operativo.

script.js
// Detectamos si el usuario tiene habilitado el modo oscuro
const getScheme = window.matchMedia('(prefers-color-scheme: dark)');

El método matchMedia del objeto window nos permite obtener fácilmente la preferencia de tema del usuario.

Por ahora no realiza ninguna acción, pero más adelante nos será muy útil para alternar automáticamente entre los temas según dicha configuración.

Nota

Con el método matchMedia podemos identificar cualquier tipo de media query para aplicar efectos o ejecutar una acción determinada.

Este método devuelve un objeto llamado MediaQueryList, que nos permite conocer si la condición definida en la media query se cumple o no, además de escuchar los cambios cuando esta varía.

Creando una función para alternar de tema

Podemos automatizar el proceso de alternancia mediante una función que agregue o elimine la clase CSS correspondiente a nuestro tema oscuro (en este caso, dark) sobre la etiqueta <html>:

script.js
function toggleDarkMode(state) {
document.documentElement.classList.toggle('dark', state);
}

La función toggleDarkMode recibe un parámetro state, el cual definiremos al momento de invocar la función.

El método toggle() del objeto classList acepta un segundo parámetro booleano opcional:

De esta forma, podemos alternar fácilmente entre el modo claro y el modo oscuro con una sola llamada a la función.

¿Porqué aplicar la clase a la etiqueta html?

En mi experiencia, es preferible establecer las clases que aplican estilos o comportamientos globales directamente en la etiqueta <html>.

Esto se debe a que, durante el proceso de análisis del documento y construcción del DOM, esta es la primera etiqueta en ser interpretada, lo que nos ofrece diversas ventajas respecto a aplicarlas sobre la etiqueta <body>.

Luego, podemos invocar la función de la siguiente manera:

script.js
toggleDarkMode(getScheme.matches);

Aquí es donde entra en juego el objeto MediaQueryList que almacenamos en la constante getScheme.

Podemos aprovechar su propiedad matches, la cual devuelve un valor booleano (true o false) dependiendo de si la preferencia del usuario coincide con la media query que estamos evaluando.

De esta forma, la clase dark se añadirá o eliminará automáticamente según corresponda.

Con lo que tenemos hasta ahora, logramos un comportamiento equivalente a utilizar la media query prefers-color-scheme en nuestros estilos CSS.

Al cargar la página, si el usuario tiene activado el modo oscuro en su sistema operativo, la clase dark se aplicará automáticamente a la etiqueta <html>.

Sin embargo, todavía necesitamos añadir la funcionalidad al botón para que el usuario pueda alternar manualmente entre los modos.

Detectando los cambios de las preferencias de los usuarios

Gracias a que el objeto MediaQueryList admite el evento change, podemos establecer una escucha para detectar cualquier cambio en la configuración del sistema operativo del usuario y alternar automáticamente el tema.

script.js
// Escuchamos los cambios en las configuración
// del sistema operativo para alternar de tema
getScheme.addEventListener('change', (event) => toggleDarkMode(event.matches));

Volvemos a llamar a la función toggleDarkMode para alternar automáticamente el tema.

El evento (event) incluye la propiedad matches, que devuelve un valor booleano (true o false) dependiendo de si la preferencia actual del usuario coincide con la media query evaluada.

De esta manera, si el usuario cambia el tema de su sistema operativo, nuestra página web se actualizará automáticamente para reflejar ese cambio.

Añadiendo funcionalidad al botón

Ahora podemos utilizar nuestro botón para ofrecer a los usuarios una forma sencilla de alternar entre el modo claro y el modo oscuro.

script.js
// Seleccionamos el botón
const buttonToggle = document.querySelector('.toggle');
buttonToggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});

Con esto, ya contamos con un botón funcional que permite alternar entre los temas.

Además, también hemos creado una función que detecta las preferencias del usuario para aplicar automáticamente el modo oscuro cuando sea necesario.

A continuación, puedes ver el código JavaScript completo:

script.js
// Seleccionamos el botón
const buttonToggle = document.querySelector('.toggle');
// Detectamos si el usuario tiene habilitado el modo oscuro
const getScheme = window.matchMedia('(prefers-color-scheme: dark)');
function toggleDarkMode(state) {
document.documentElement.classList.toggle('dark', state);
}
toggleDarkMode(getScheme.matches);
// Escuchamos los cambios en las configuración del
// del sistema operativo para alternar de tema
getScheme.addEventListener('change', (event) => toggleDarkMode(event.matches));
buttonToggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});

Ahora debemos resolver un detalle importante: hacer que la preferencia del usuario sea persistente.

Por ejemplo, si el usuario elige el tema oscuro y luego vuelve a ingresar al sitio, actualmente su elección no se conserva. Vamos a solucionarlo.

Haciendo el modo oscuro persistente

Antes de la llegada de las nuevas API de HTML5, la única forma de almacenar información del usuario era mediante cookies.

Sin embargo, debido a sus limitaciones —y a que su gestión se realiza del lado del servidor—, hoy en día resulta más práctico utilizar la API localStorage, que nos permite guardar datos directamente en el navegador del usuario.

De esta manera, podremos almacenar la preferencia del tema elegido (claro u oscuro) para mantenerla incluso después de recargar o volver a visitar la página.

A continuación, realizaremos algunas pequeñas modificaciones en el código anterior:

script.js
​​let darkModeState = false;
8 collapsed lines
// Seleccionamos el botón
const buttonToggle = document.querySelector('.toggle');
// Detectamos si el usuario tiene habilitado el modo oscuro
const getScheme = window.matchMedia('(prefers-color-scheme: dark)');
function toggleDarkMode(state) {
document.documentElement.classList.toggle('dark', state);
darkModeState = state;
}
function setSchemeState(state) {
localStorage.setItem('dark-mode', state);
}
toggleDarkMode(getScheme.matches);
toggleDarkMode(localStorage.getItem('dark-mode') === 'true');
3 collapsed lines
// Escuchamos los cambios en las configuración del
// del sistema operativo para alternar de tema
getScheme.addEventListener('change', (event) => toggleDarkMode(event.matches));
buttonToggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
darkModeState = !darkModeState;
toggleDarkMode(darkModeState);
setSchemeState(darkModeState);
});

Dentro de la función setSchemeState, hacemos uso de localStorage.

Con el método setItem(), establecemos el valor que deseamos guardar. Este método recibe dos parámetros:

  1. El nombre de la clave (en este caso lo llamé "dark-mode", aunque puedes usar el nombre que prefieras).
  2. El valor a almacenar, que corresponde al estado actual del tema (true o false).

De esta forma, tendremos un botón completamente funcional que alterna el tema y conserva la preferencia del usuario, sin afectar la funcionalidad de detección automática del sistema operativo.

Además, cualquier cambio en la configuración del sistema también se almacenará en localStorage.

Sin embargo, aún debemos resolver un pequeño inconveniente: cuando el modo oscuro está activado y el usuario navega hacia otra página del sitio web, puede producirse un breve destello de color claro antes de que se aplique nuevamente el tema oscuro.

Vista previa

¿Cómo evitar el parpadeo entre páginas?

El parpadeo o destello entre páginas ocurre debido a cómo funciona nuestro código: al cargar la página, el navegador necesita leer el valor almacenado en localStorage antes de aplicar la clase dark. Este proceso puede tomar un breve tiempo, dependiendo de la optimización y tamaño del sitio web.

Para evitar este efecto visual, podemos agregar un pequeño fragmento de JavaScript en línea dentro de la etiqueta <head>, antes de importar nuestra hoja de estilos:

index.html
<!DOCTYPE html>
<html lang="es">
<head>
<script>
if (localStorage.getItem('dark-mode') === 'true') {
document.documentElement.classList.add('dark');
}
</script>
<link rel="stylesheet" href="css/style.css" />
</head>
</html>

De esta forma, cada vez que se cargue una página, se evaluará inmediatamente la preferencia del usuario. Si el valor almacenado es true, la clase dark se aplicará de forma instantánea a la etiqueta <html>.

Así, cuando el navegador descargue y procese los estilos CSS, el modo oscuro ya estará activo y no se producirá el destello.

Con esto, hemos resuelto todos los detalles relacionados con el cambio de tema en nuestro sitio web, asegurando una experiencia fluida y agradable para los usuarios, quienes ahora pueden elegir con qué tema prefieren visualizar la página.

Vista previa

Bonus: Creando un atajo de teclado

Si quieres ir un paso más allá en términos de accesibilidad y ofrecer a tus usuarios una forma rápida de alternar entre los modos de color, una excelente opción es implementar un atajo de teclado.

Es importante elegir cuidadosamente la combinación de teclas para evitar conflictos con los atajos predefinidos del navegador o del sistema operativo.

Para crear este atajo, agregaremos un escuchador de eventos al objeto window, utilizando el evento keydown.

Podemos complementar el código que ya hemos desarrollado anteriormente con el siguiente fragmento:

script.js
window.addEventListener('keydown', (e) => {
// Detectamos si las teclas Alt, Shift y D están presionadas
if (e.altKey === true && e.shiftKey === true && e.key === 'D') {
darkModeState = !darkModeState;
toggleDarkMode(darkModeState);
setSchemeState(darkModeState);
}
});

Como habrás notado, el código es muy similar al que utilizamos para el botón; la única diferencia radica en la acción.

El atajo de teclado que elegí es Alt + Shift + D, ya que no entra en conflicto con los atajos del navegador y resulta muy cómodo de utilizar.

Vista previa

Haz click dentro del preview y presiona el atajo de teclado para alternar entre el modo claro y el modo oscuro (no compatible en móvil).

Con solo unas pocas líneas de código, logramos construir un snippet de código que permite alternar entre el modo claro y el modo oscuro de forma sencilla para los usuarios, conserva su preferencia y, además, ofrece una opción rápida mediante un atajo de teclado.

Espero que puedas aplicar este enfoque en tus futuros proyectos y mejorar la experiencia visual de tus sitios web.

Otros artículos

PWA vs Aplicaciones Nativas, ¿Cuál elegir?

Conoce todas las Técnicas de Renderizado de Sitios Web

Optimizar la Carga de Imágenes en tu Sitio Web con Placeholders

¿Qué son las Progressive Web Apps?

Core Web Vitals: Las Métricas de Optimización Web

Aprende a crear modales sin usar z-index con el elemento dialog