Cómo implementar modo oscuro con variables CSS paso a paso

  • Las variables CSS permiten centralizar colores y estilos, facilitando la creación de temas claros y oscuros sin duplicar reglas.
  • La media query prefers-color-scheme adapta el modo oscuro automáticamente a la preferencia del sistema operativo del usuario.
  • Un selector manual con clases o data-theme, combinado con JavaScript y localStorage, ofrece control total y persistente del tema.
  • Cuidar contraste, tipografía y feedback visual en modo oscuro mejora la accesibilidad y la experiencia de usuario en todo tipo de dispositivos.

Implementar modo oscuro con variables CSS

Si pasas muchas horas delante de la pantalla, seguro que has agradecido más de una vez poder activar el modo oscuro en alguna app o web. No es solo una cuestión de estética: un buen tema oscuro puede reducir la fatiga visual, ayudar con la concentración y, en algunos dispositivos, incluso ahorrar algo de batería.

La buena noticia es que montar un modo oscuro en tu sitio no es ningún drama. Con una combinación de variables CSS, media queries y un poco de JavaScript opcional puedes crear temas claros y oscuros flexibles, accesibles y fáciles de mantener, sin reescribir tu hoja de estilos de arriba abajo.

Qué es exactamente el modo oscuro y por qué usarlo

Cuando hablamos de modo oscuro o dark mode nos referimos a un esquema de color en el que los fondos pasan a ser oscuros y el contenido principal se muestra en tonos claros. Es lo contrario al diseño clásico de texto oscuro sobre fondo blanco, que ha sido la norma en la web durante años.

Los sistemas operativos modernos (Windows, macOS, iOS, Android…) permiten configurar una preferencia global de tema claro u oscuro. Muchos usuarios activan el modo oscuro en entornos con poca luz, por comodidad o por simple preferencia estética. Si tu web respeta esa elección, estás dando un plus importante en experiencia de usuario y accesibilidad.

Además, en pantallas OLED y AMOLED, los píxeles negros o casi negros consumen menos energía, así que un modo oscuro bien planteado puede ayudar a ahorrar batería, sobre todo en móviles.

Principios básicos de diseño en modo oscuro

No se trata solo de invertir colores. Para que un tema oscuro funcione, hay que cuidar varios detalles de diseño y accesibilidad, porque un contraste mal resuelto puede ser tan molesto como una interfaz demasiado brillante.

Contraste y legibilidad

El primer punto crítico es el contraste entre fondo y texto. Necesitas que el contenido sea fácil de leer sobre fondos oscuros sin provocar deslumbramientos. Las pautas WCAG recomiendan un ratio mínimo de 4.5:1 para texto normal, aunque en modo oscuro es muy habitual apuntar a valores en torno a 7:1 para mejorar la comodidad.

Un caso típico sería usar un fondo casi negro y texto gris claro, no blanco puro. Por ejemplo, algo como background-color: #121212 y color: #E0E0E0 suele dar un resultado suave y legible, evitando el impacto de un blanco puro sobre negro total.

Elección de paleta y colores de acento

En temas oscuros conviene evitar colores muy saturados y chillones, porque sobre un fondo oscuro tienden a brillar demasiado y cansan la vista rápidamente. Es mejor apostar por tonos más suaves y ligeramente desaturados para botones, enlaces y elementos destacados.

También es recomendable huir del negro absoluto (#000000) y del blanco puro (#FFFFFF). Un negro ligeramente elevado y un blanco algo atenuado hacen que el contraste sea más agradable. Por ejemplo, en lugar de usar blanco puro para un acento, se puede elegir algo como #BB86FC o un dorado suave que destaque sin resultar estridente.

Accesibilidad y preferencias del usuario

Diseñar un modo oscuro implica pensar en usuarios con diferentes capacidades visuales. No basta con que “se vea bonito”; tiene que ser usable también para personas con baja visión o sensibilidad al brillo. Esto incluye revisar contrastes, estados de foco, estados de error y elementos interactivos.

Además del color, es importante respetar otras preferencias como reduce motion. Si el usuario ha indicado que prefiere menos animaciones, tus transiciones entre temas deberían adaptarse usando, por ejemplo, una media query prefers-reduced-motion para reducir o eliminar animaciones en esos casos.

Por qué las variables CSS son la clave del modo oscuro

Las variables CSS (propiedades personalizadas) son ideales para manejar un sistema de temas porque permiten centralizar todos los colores y estilos dependientes del tema en un solo sitio. En lugar de repetir valores por toda la hoja de estilos, defines unas cuantas variables y luego las reutilizas con la función var().

Lo habitual es declarar estas variables en la pseudoclase :root, que apunta al elemento <html>. Por ejemplo, puedes definir colores base para el tema claro de esta forma:

:root {
  --color-fondo: #ffffff;
  --color-texto: #333333;
  --color-acento: #007BFF;
}

A partir de ahí, el resto de la hoja de estilos usa estas propiedades personalizadas en lugar de valores fijos:

body {
  background-color: var(--color-fondo);
  color: var(--color-texto);
}

a,
button {
  color: var(--color-acento);
}

Si decides cambiar la paleta o añadir un tema oscuro, solo tendrás que reasignar los valores de esas variables en otro contexto (clases, atributos, media queries…) en lugar de revisar cada regla una por una.

Implementar modo oscuro con media queries (prefers-color-scheme)

La forma más respetuosa con el usuario es dejar que sea el sistema el que mande. La media query prefers-color-scheme te permite adaptar el tema automáticamente a la preferencia global del dispositivo.

Hay dos enfoques muy usados: definir versiones separadas para claro y oscuro, o tratar el modo claro como valor por defecto y sobreescribir solo cuando el sistema pida oscuro.

Definir variables por modo con media queries

Un patrón muy directo es declarar variables base y luego cambiarlas dentro de media queries específicas:

/* Tema claro por defecto */
:root {
  --body-bg: #FFFFFF;
  --body-color: #000000;
}

/* Tema oscuro cuando el usuario lo prefiere */
@media (prefers-color-scheme: dark) {
  :root {
    --body-bg: #000000;
    --body-color: #FFFFFF;
  }
}

En este ejemplo, el body usará siempre background: var(–body-bg) y color: var(–body-color). Lo único que cambia es el valor de las variables según la preferencia del sistema. Si en el futuro se añadieran más valores a prefers-color-scheme, seguirías teniendo un modo claro por defecto correctamente definido.

También puedes ser aún más explícito y especificar ambos modos:

@media (prefers-color-scheme: light) {
  :root {
    --body-bg: #FFFFFF;
    --body-color: #000000;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --body-bg: #000000;
    --body-color: #FFFFFF;
  }
}

Con esto consigues que tu sitio se adapte automáticamente al modo claro u oscuro elegido en el sistema operativo, sin que el usuario tenga que tocar ningún ajuste dentro de la web.

Modo oscuro sin JavaScript usando :has() y un checkbox

Si quieres ofrecer un interruptor de tema dentro de la página pero no te apetece tirar de JavaScript, puedes apoyarte en el selector relacional :has(). Este selector permite aplicar estilos a un elemento padre cuando contiene un hijo que cumple cierta condición, en este caso un checkbox marcado.

La idea es definir primero los colores claros como base:

:root {
  --bg-color: #ffffff;
  --text-color: #222222;
}

body {
  background: var(--bg-color);
  color: var(--text-color);
  transition: background 0.3s, color 0.3s;
}

Y después aprovechar :has() para cambiar las variables cuando el checkbox del modo oscuro está activado:

body:has(#darkmode-toggle:checked) {
  --bg-color: #1e1e1e;
  --text-color: #f5f5f5;
}

En el HTML solo necesitas un checkbox que sirva como interruptor:

<input type="checkbox" id="darkmode-toggle" />
<label for="darkmode-toggle">Modo oscuro</label>

Cuando el usuario marca la casilla, el selector body:has(#darkmode-toggle:checked) entra en juego y las variables cambian a los valores oscuros. El gran “pero” de este enfoque es que la preferencia no se conserva entre páginas ni visitas, porque no se usa almacenamiento ni lógica extra.

Interruptor de tema con clases, JavaScript y localStorage

Si buscas un sistema más completo, lo habitual es basarse en añadir o quitar una clase (o un atributo de datos) en el elemento raíz y gestionar el cambio con JavaScript, guardando además la elección del usuario.

Definir variables según la clase de tema

Primero defines los colores claros por defecto en :root y luego una variante oscura cuando el elemento raíz tiene una clase concreta, como tema-oscuro:

:root {
  --color-fondo: #ffffff;
  --color-texto: #000000;
  --color-principal: #007bff;
}

:root.tema-oscuro {
  --color-fondo: #333333;
  --color-texto: #ffffff;
  --color-principal: #BB86FC;
}

El resto de estilos siguen usando var(–color-fondo), var(–color-texto) y demás variables, de modo que cambiar de tema equivale a alternar la clase tema-oscuro en <html> o <body>.

Crear el interruptor en HTML y dar vida con JavaScript

En el marcado puedes usar un simple checkbox, un botón o un toggle más elaborado. Un caso sencillo podría ser:

<input type="checkbox" id="interruptor-tema" />
<label for="interruptor-tema">Tema oscuro</label>

Con JavaScript, escuchas el cambio del interruptor y añades o quitas la clase en el elemento raíz, además de guardar la preferencia en localStorage para no perderla al navegar o recargar:

// Al cargar la página, aplicamos el tema guardado
if (localStorage.getItem('tema') === 'oscuro') {
  document.documentElement.classList.add('tema-oscuro');
  document.getElementById('interruptor-tema').checked = true;
}

// Actualizamos cuando el usuario cambia el toggle
document.getElementById('interruptor-tema').addEventListener('change', function () {
  if (this.checked) {
    document.documentElement.classList.add('tema-oscuro');
    localStorage.setItem('tema', 'oscuro');
  } else {
    document.documentElement.classList.remove('tema-oscuro');
    localStorage.setItem('tema', 'claro');
  }
});

Con este enfoque consigues un modo oscuro persistente y controlado por el usuario, sin dejar de aprovechar las variables CSS para mantener el código limpio.

Usar atributos de datos o data-theme como selector

En lugar de una clase, también puedes usar un atributo como data-theme. Esto es especialmente cómodo si trabajas con frameworks o utilidades como Tailwind CSS, que permiten configurar la variante dark para que se active cuando haya un selector del tipo en lugar de la media query.

En CSS nativo el patrón sería muy similar:

:root {
  --color-fondo: #FFFFFF;
  --color-texto: #333333;
  --color-acento: #007BFF;
}

 {
  --color-fondo: #121212;
  --color-texto: #E0E0E0;
  --color-acento: #BB86FC;
}

Aplicando el atributo al elemento raíz:

<html lang="es" data-theme="dark">

Todo el sitio pasará a usar los valores oscuros. Si cambias el atributo a light o lo eliminas, volverás al tema claro. El JavaScript para alternar entre uno y otro no cambia mucho respecto al caso con clases: simplemente llamas a setAttribute(‘data-theme’, ‘dark’) o ‘light’ en función de la elección del usuario.

Combinar tema del sistema y selector manual

Un escenario muy habitual es querer soportar tres estados: tema claro, tema oscuro y “usar el tema del sistema”. Para ello puedes combinar prefers-color-scheme con clases o atributos, y usar window.matchMedia() para detectar el modo del sistema cuando el usuario elige seguir esa opción.

La idea general es:

  • Si el usuario selecciona explícitamente claro u oscuro, guardas esa preferencia y la aplicas con una clase o atributo (por ejemplo, data-theme=»dark»).
  • Si elige “sistema”, no fuerzas ningún tema concreto y dejas que sean las media queries prefers-color-scheme las que decidan los valores por defecto.
  • Opcionalmente, escuchas cambios en matchMedia(‘(prefers-color-scheme: dark)’) para reaccionar si el usuario cambia el tema del sistema en caliente.

Este patrón encaja muy bien con herramientas como Tailwind, donde puedes decidir si la variante dark se basa en prefers-color-scheme o en una clase/atributo, e incluso sincronizarlo con una preferencia almacenada en cliente o en servidor.

modo oscuro windows 11
Artículo relacionado:
Cómo activar el modo oscuro en Windows 11

Más allá del color: tipografía y elementos de la interfaz

Activar el modo oscuro no debería limitarse a cambiar cuatro colores. Hay otros detalles que merece la pena ajustar para que la experiencia sea realmente cómoda y coherente.

En primer lugar, es recomendable aumentar ligeramente el tamaño de la fuente y el interlineado en temas oscuros, porque el texto sobre fondo oscuro tiende a percibirse más denso. Un pequeño incremento en el tamaño base o en el line-height puede marcar la diferencia.

También conviene revisar botones, enlaces, tarjetas y otros componentes de UI. En modo oscuro puedes usar sombras suaves, bordes sutiles o gradientes muy ligeros para dar sensación de profundidad sin caer en fondos planos que hagan que todo parezca pegado al mismo nivel.

No olvides el feedback visual: estados hover, focus y active tienen que seguir siendo claros. En un tema oscuro, un cambio de color o un ligero aumento de brillo en el color de acento suele funcionar mejor que una sombra demasiado agresiva.

Testing, accesibilidad y herramientas de validación

Una vez tengas montado tu sistema de temas, toca probarlo en serio. No basta con verlo en tu navegador principal; es importante verificar que el modo oscuro se comporta bien en distintos dispositivos, navegadores y configuraciones.

Algunas herramientas útiles para esta fase son las DevTools de Chrome (prueba cómo forzar modo oscuro en Google Chrome), Firefox o Safari, que incluyen opciones para simular prefers-color-scheme sin necesidad de cambiar el tema de tu sistema. También puedes usar Lighthouse para revisar accesibilidad y contrastes, o servicios de pruebas cruzadas como CrossBrowserTesting para verificar que no se rompen estilos en navegadores menos habituales.

Además de las pruebas técnicas, merece la pena recabar feedback real de usuarios. Pequeñas encuestas, formularios dentro del propio sitio o sesiones de test con usuarios pueden revelar problemas de legibilidad, contrastes insuficientes o elementos que pasan desapercibidos en el modo oscuro.

Implementar un modo oscuro moderno con variables CSS te permite respetar las preferencias del sistema, ofrecer un selector de tema flexible, mantener el código limpio y cuidar la accesibilidad, algo cada vez más valorado por usuarios y buscadores. Con una buena base de variables, media queries bien planteadas y, cuando haga falta, un toque de JavaScript para la persistencia, tu sitio podrá moverse con soltura entre temas claros y oscuros sin volverse un caos de estilos imposibles de mantener.