Modo oscuro con CSS y JavaScript: sistema y preferencia

Implementa modo oscuro con JavaScript y CSS. Respeta el sistema, guarda una elección voluntaria y admite volver a la configuración automática.

Tres opciones en lugar de un estado ambiguo

El modo oscuro con JavaScript necesita distinguir entre una preferencia explícita y seguir al sistema. Un interruptor binario puede dejar esa diferencia poco clara. El selector del ejemplo ofrece sistema, claro y oscuro. Así una persona puede probar otro aspecto y volver después al comportamiento automático sin borrar datos desde las herramientas del navegador.

Variables para conservar coherencia

Define colores por función: fondo, texto, superficie, borde y enlace. Cambiar solo el fondo del body deja tarjetas y formularios con combinaciones inconsistentes. Cada componente debe utilizar los valores apropiados. No inviertas toda la página con un filtro, porque también alterarías fotografías y colores que tienen un significado propio. Diseñar dos apariencias requiere revisar las relaciones, no únicamente sustituir blanco por negro.

Código completo para probar en tu navegador

Copia este documento en un archivo index.html, guarda los cambios y ábrelo en el navegador. El ejemplo no requiere bibliotecas.

<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Modo oscuro con CSS y JavaScript: sistema y preferencia</title>
<style>
*{box-sizing:border-box}body{font:16px/1.6 system-ui,sans-serif;max-width:900px;margin:2rem auto;padding:1rem;color:#17233b}label{display:block;font-weight:700;margin-top:1rem}input,textarea,select,button{font:inherit;padding:.65rem;max-width:100%}textarea{width:100%}button{cursor:pointer}button:focus-visible,a:focus-visible{outline:3px solid #173b94;outline-offset:3px}[hidden]{display:none!important}[role=tab][aria-selected=true]{background:#173b94;color:white}[role=tab]{border:1px solid #bdcbe2;border-radius:.5rem}[role=tabpanel]{padding:1rem;border:1px solid #bdcbe2}
</style>
</head>
<body>
<label for="tema">Apariencia</label>
<select id="tema"><option value="sistema">Sistema</option><option value="claro">Claro</option><option value="oscuro">Oscuro</option></select>
<style>
:root { color-scheme: light; --fondo: #fff; --texto: #17233b; }
:root[data-tema="oscuro"] { color-scheme: dark; --fondo: #101827; --texto: #eef3ff; }
body { background: var(--fondo); color: var(--texto); }
</style>
<script>
const control = document.querySelector('#tema');
const sistema = matchMedia('(prefers-color-scheme: dark)');
let eleccion = 'sistema';
try { eleccion = localStorage.getItem('mcp-tema') || 'sistema'; } catch {}
if (!['sistema', 'claro', 'oscuro'].includes(eleccion)) eleccion = 'sistema';
control.value = eleccion;
function aplicar() {
 document.documentElement.dataset.tema = eleccion === 'sistema'
  ? (sistema.matches ? 'oscuro' : 'claro') : eleccion;
}
control.addEventListener('change', () => {
 eleccion = control.value;
 try { localStorage.setItem('mcp-tema', eleccion); } catch {}
 aplicar();
});
sistema.addEventListener('change', aplicar);
aplicar();
</script>
</body>
</html>

Responder a cambios del sistema

MatchMedia informa de la preferencia y permite escuchar sus cambios. La función vuelve a calcular el tema, pero conserva una elección explícita. Si la persona eligió claro, un cambio del sistema no debería imponer oscuro. Esa separación evita que la interfaz parezca ignorar una decisión voluntaria. Comprueba el estado automático mientras modificas la preferencia del equipo.

Persistencia que no bloquea la página

El almacenamiento puede fallar o contener un valor antiguo. Por eso el ejemplo trata errores y valida las opciones recuperadas. La apariencia debe funcionar durante la sesión aunque no pueda guardarse para otra visita. No muestres un error alarmante si la única consecuencia es volver a seguir al sistema en la siguiente carga. La prioridad es conservar lectura y controles utilizables.

Formularios e imágenes

Color-scheme informa al navegador de la apariencia para componentes que administra, pero no diseña todos tus controles personalizados. Revisa campos, opciones, iconos y mensajes. Algunas imágenes transparentes se prepararon para blanco y pueden mostrar bordes incómodos en oscuro. No reemplaces información ni logotipos sin comprobar su versión adecuada. Una imagen clara sobre una superficie oscura puede necesitar un marco neutro.

Evitar destellos al cargar

Si la elección guardada se aplica tarde, puede aparecer primero el tema opuesto. En producción conviene aplicar el estado inicial temprano con una implementación compatible con tu sistema de carga y su política de scripts. No añadas varias inicializaciones que compitan. La primera representación y el control visible deben utilizar la misma regla para no mostrar claro mientras el selector dice oscuro.

Revisión de contraste

Mide texto, enlaces, foco y estados de error en ambos temas. Una combinación que funciona en claro no conserva automáticamente su contraste en oscuro. No debilites demasiado los bordes si son necesarios para reconocer un campo. Prueba también selección, hover y contenido desactivado. El tema debe permitir realizar la misma tarea, no ser simplemente una preferencia decorativa.

Pruebas de funcionamiento

Elige oscuro, recarga y comprueba que se conserva. Vuelve a sistema y cambia la preferencia del equipo. Simula almacenamiento no disponible y verifica que los controles siguen respondiendo. Introduce un valor antiguo en la clave de pruebas y comprueba la recuperación. Finalmente revisa que otras funciones del sitio no utilizan la misma clave con otro formato: un prefijo propio reduce colisiones.

Elegir superficies y bordes además del fondo

Cambiar únicamente el fondo general puede dejar tarjetas blancas, campos ilegibles o divisores demasiado brillantes. Define variables para superficie, borde, texto secundario y foco cuando el diseño lo necesite. Comprueba cada combinación en sus estados reales: enlaces visitados, botones desactivados y mensajes de error. No asumas que invertir un color produce una pareja accesible. También es importante distinguir superficies contiguas sin depender de sombras que casi desaparecen en oscuro. Una paleta pequeña y comprobada suele ser más fácil de mantener que excepciones por componente distribuidas por varios archivos.

Qué conserva y qué no conserva localStorage

La elección guardada corresponde a ese origen y a ese navegador. No se sincroniza automáticamente con otra cuenta, otro dispositivo o un dominio diferente. Una sesión privada o una política del navegador puede limitar su duración o disponibilidad. El bloque try permite que la página funcione incluso cuando no se puede guardar. En ese caso la selección sigue aplicada durante la visita, pero podría no mantenerse después. No conviertas una preferencia visual opcional en un requisito para usar el sitio ni muestres un error alarmante por una persistencia que no es esencial.

El modo Sistema debe seguir siendo automático

Cuando la persona elige Sistema, guardamos esa intención y calculamos la apariencia a partir de matchMedia. No reemplazamos esa elección por el resultado oscuro o claro que existía en ese instante. Si lo hicieras, el usuario perdería la conexión con los cambios posteriores del dispositivo. El evento change de la consulta actualiza la apariencia, mientras que aplicar respeta una selección explícita. Puedes comprobarlo cambiando la preferencia del sistema con la página abierta: solamente quienes eligieron Sistema deberían ver el cambio reflejado como una nueva decisión automática.

Probar componentes que vienen de fuera

Un mapa, un vídeo incrustado o un formulario externo puede tener una apariencia independiente. El atributo data-tema de tu documento no cambia automáticamente el contenido de otro origen. Revisa las opciones de configuración del servicio y evita filtros globales que alteren fotografías o iconos sin control. Si un componente permanece claro, puedes colocarlo en una superficie deliberada que mantenga legibilidad y límites. No prometas un modo oscuro completo hasta comprobar todas las pantallas importantes, incluidos diálogos, mensajes de validación y páginas que cargan estilos separados.

Conservar una salida comprensible en cada opción

Revisa el selector después de recargar y confirma que muestra la intención guardada. Cuando el usuario eligió Sistema, debe seguir diciendo Sistema aunque en ese momento se vea oscuro. Esa etiqueta ayuda a explicar por qué la apariencia puede cambiar más tarde. Si incorporas un botón en lugar del selector, necesitarás comunicar también cómo regresar a la opción automática. No escondas una tercera posibilidad detrás de una pulsación poco evidente. Una preferencia útil debe poder elegirse, entenderse y deshacerse desde el mismo lugar sin borrar datos del navegador ni buscar instrucciones adicionales.

Guías relacionadas para seguir practicando

Puedes ampliar esta práctica con Guardar preferencias con localStorage y Comprobar el contraste.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

logo mastercode pro
Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.

Puedes ver mas detalle en nuestra pagina de privcidad