Back

Cómo Crear un Selector de Modo Oscuro con CSS y JavaScript

Cómo Crear un Selector de Modo Oscuro con CSS y JavaScript

El modo oscuro se ha vuelto esencial para los sitios web modernos. Los usuarios esperan poder cambiar de tema según su entorno, la hora del día o sus preferencias personales. Este tutorial te muestra cómo crear un selector de modo oscuro robusto que respeta las preferencias del sistema, recuerda las elecciones del usuario y aprovecha las características modernas de CSS para un rendimiento óptimo.

Puntos Clave

  • Construye un selector de modo oscuro que respeta las preferencias del sistema y persiste las elecciones del usuario
  • Utiliza propiedades personalizadas de CSS y la propiedad color-scheme para una tematización eficiente
  • Implementa JavaScript que previene el destello de contenido sin estilo al cargar la página
  • Aplica las mejores prácticas de accesibilidad para navegación por teclado y lectores de pantalla

Configuración de la Estructura HTML

Comienza con HTML semántico que incluya un botón de alternancia y declare el tema inicial:

<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="color-scheme" content="light dark">
    <title>Dark Mode Toggle Demo</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <button type="button" id="theme-toggle" aria-label="Toggle dark mode">
        <span class="toggle-text">Dark</span>
    </button>
    <!-- Your content here -->
</body>
</html>

El atributo data-theme en el elemento HTML controla nuestro tema. La etiqueta meta color-scheme le indica al navegador que adapte los elementos nativos de la interfaz como las barras de desplazamiento y los controles de formulario.

Implementación de CSS con Características Modernas

Uso de Propiedades Personalizadas de CSS y color-scheme

Define tus tokens de color utilizando propiedades personalizadas de CSS, luego aprovecha la propiedad CSS color-scheme para la tematización de elementos nativos:

:root {
    color-scheme: light dark;
    
    /* Light theme colors (default) */
    --bg-color: #ffffff;
    --text-color: #213547;
    --accent-color: #0066cc;
}

[data-theme="dark"] {
    --bg-color: #1a1a1a;
    --text-color: #e0e0e0;
    --accent-color: #66b3ff;
}

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

Respetando las Preferencias del Sistema con prefers-color-scheme

Añade soporte para prefers-color-scheme para coincidir automáticamente con la configuración del sistema:

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg-color: #1a1a1a;
        --text-color: #e0e0e0;
        --accent-color: #66b3ff;
    }
}

La Función Moderna light-dark()

Para navegadores que soportan la función light-dark(), puedes simplificar las definiciones de color:

:root {
    color-scheme: light dark;
}

body {
    background-color: light-dark(#ffffff, #1a1a1a);
    color: light-dark(#213547, #e0e0e0);
}

Esta función selecciona automáticamente el color apropiado basándose en el esquema de color activo, reduciendo la duplicación de código.

JavaScript para Alternancia y Persistencia

Prevención del Destello de Tema al Cargar

Coloca este script inmediatamente después de la etiqueta de apertura <body> para prevenir el destello de contenido sin estilo:

<script>
    // Apply saved theme immediately to prevent flash
    (function() {
        const saved = localStorage.getItem('theme');
        if (saved) {
            document.documentElement.setAttribute('data-theme', saved);
        } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
            document.documentElement.setAttribute('data-theme', 'dark');
        }
    })();
</script>

Implementación Completa del Modo Oscuro

Añade este JavaScript para la funcionalidad de alternancia:

const toggle = document.getElementById('theme-toggle');
const html = document.documentElement;

// Get current theme
function getTheme() {
    return html.getAttribute('data-theme') || 
           (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
}

// Set theme and update UI
function setTheme(theme) {
    html.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
    updateToggleText(theme);
}

// Update button text
function updateToggleText(theme) {
    const text = toggle.querySelector('.toggle-text');
    text.textContent = theme === 'dark' ? 'Light' : 'Dark';
    toggle.setAttribute('aria-label', `Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`);
}

// Initialize
updateToggleText(getTheme());

// Handle toggle click
toggle.addEventListener('click', () => {
    const current = getTheme();
    setTheme(current === 'dark' ? 'light' : 'dark');
});

// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
    if (!localStorage.getItem('theme')) {
        setTheme(e.matches ? 'dark' : 'light');
    }
});

Accesibilidad y Mejores Prácticas

Características Esenciales de Accesibilidad

Tu selector de modo oscuro debe ser accesible mediante teclado y estar correctamente etiquetado:

#theme-toggle:focus-visible {
    outline: 2px solid var(--accent-color);
    outline-offset: 2px;
}

Asegura un contraste de color suficiente en ambos temas. El modo oscuro no significa bajo contraste—mantén al menos los estándares WCAG AA (4.5:1 para texto normal).

Qué Evitar

No uses filter: invert(1) en toda la página—invierte imágenes y medios innecesariamente. Evita fondos negros puros (#000000) en modo oscuro. Usa grises oscuros (#1a1a1a) para una mejor legibilidad. Nunca dependas únicamente de JavaScript sin respaldos CSS.

Prueba de tu Implementación

Prueba tu selector de modo oscuro en diferentes escenarios:

  1. Limpia localStorage y verifica que la detección de preferencias del sistema funcione
  2. Alterna el tema y actualiza para confirmar la persistencia
  3. Cambia las preferencias del sistema mientras el sitio está abierto
  4. Verifica las proporciones de contraste usando las DevTools del navegador
  5. Prueba la navegación por teclado y los anuncios del lector de pantalla

Conclusión

Una solución de modo oscuro bien implementada respeta las preferencias del usuario, persiste las elecciones y aprovecha las características modernas de CSS. Al combinar la propiedad color-scheme, propiedades personalizadas y JavaScript mínimo, creas un selector de tema que es eficiente, accesible y fácil de usar. La función light-dark() simplifica la gestión de colores para navegadores modernos mientras que los respaldos aseguran la compatibilidad.

Recuerda: la mejor implementación de modo oscuro es invisible para los usuarios—simplemente funciona exactamente como ellos esperan.

Preguntas Frecuentes

Sí, puedes usar media queries de CSS con prefers-color-scheme para detectar las preferencias del sistema. Sin embargo, se necesita JavaScript para guardar las preferencias del usuario y proporcionar un botón de alternancia manual que anule la configuración del sistema.

Esto ocurre cuando JavaScript se ejecuta después de que la página se renderiza. Coloca un script inmediatamente después de la etiqueta body que verifique localStorage y aplique el tema guardado antes de que la página se pinte para prevenir este destello.

No, el negro puro puede causar fatiga visual y dificultar la lectura del texto. Usa grises oscuros como #1a1a1a o #121212 en su lugar. Estos colores reducen la fatiga mientras mantienen buenas proporciones de contraste para la accesibilidad.

Evita usar filter invert en las imágenes. En su lugar, proporciona versiones alternativas de las imágenes para modo oscuro o usa PNG transparentes con colores que funcionen en ambos fondos. Para gráficos complejos, considera usar máscaras CSS o SVG con currentColor.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay