Un botón de activación del modo oscuro permite que los usuarios anulen el esquema de colores del sistema para un sitio.
La anulación se aplica actualizando <meta name="color-scheme">: light dark significa "seguir la preferencia del sistema", light o dark fijan un esquema explícito.
Los colores (light-dark()), los colores del sistema y la IU nativa se basan en la propiedad color-scheme de CSS, por lo que el botón de activación funciona manteniendo esa propiedad sincronizada con <meta> (consulta Refleja la anulación en CSS).
¿Un botón de activación persistente, un control de configuración o ningún botón de activación?
La mayoría de los sitios no necesitan un botón de activación persistente del esquema de colores en la IU del sitio (encabezado, navegación, pie de página, etcétera). Sigue la preferencia del sistema de forma predeterminada y, si se necesita una anulación manual, coloca el control en una superficie de configuración independiente: una página, un panel o una superposición de configuración.
Fijar un esquema en el SO es una intención legítima, pero poco frecuente, y las intenciones poco frecuentes deben estar detrás de la divulgación progresiva, no en un control que esté visible en todo momento.
Los botones de activación persistentes se encuentran principalmente en sitios orientados a desarrolladores o adyacentes a ellos (p.ej., para diseñadores), en los que cambiar de esquema es una tarea mucho más frecuente que para el usuario web promedio. NO agregues uno solo porque es un patrón común en los sitios para desarrolladores: el interés del desarrollador en este control no es representativo de los usuarios típicos.
Implementación
Primero, sigue todos los pasos que se indican en Modo oscuro para admitir ambos esquemas, de forma predeterminada, según la preferencia del sistema.
Modelo de datos
El modelo de datos para el botón de activación del modo oscuro suele incluir tres estados:
- Sistema (sin valor almacenado): La IU sigue el esquema de colores del sistema. Idealmente, esta debería ser la opción predeterminada.
- Claro (se almacena como
"light"): La IU usa el tema claro, independientemente del esquema de colores del sistema. - Oscuro (se almacena como
"dark"): La IU usa el tema oscuro, independientemente del esquema de colores del sistema.
Por lo general, este estado se almacena en localStorage y se refleja en un elemento <meta name="color-scheme"> en el encabezado del documento.
Refleja la anulación en CSS
El <meta> es solo una sugerencia de presentación: cualquier declaración color-scheme en el CSS del autor la anula.
Dado que el modo oscuro establece color-scheme: light dark en :root, actualizar solo <meta> es una operación nula: CSS gana.
Vuelve a asignar el <meta> a la propiedad para que la cascada lo siga:
:root {
color-scheme: light dark;
&:has(> head > meta[name="color-scheme"][content="dark"]) {
color-scheme: dark;
}
&:has(> head > meta[name="color-scheme"][content="light"]) {
color-scheme: light;
}
}
NO codifiques color-scheme: light ni color-scheme: dark como la raíz predeterminada; la declaración base DEBE permanecer como light dark.
El CSS NO DEBE depender de JS: si JS nunca se ejecuta, el <meta> permanece como light dark y el sitio sigue la preferencia del sistema, por lo que no se rompe nada.
Persistencia y prevención de FOUC
Conserva la anulación en localStorage y quita la entrada cuando vuelvas a la configuración predeterminada del sistema.
Para evitar que se muestre el esquema incorrecto, aplica el valor almacenado en una secuencia de comandos intercalada (NO type=module, NO defer) ubicada inmediatamente después del elemento <meta>:
<meta name="color-scheme" content="light dark">
<script>
document.querySelector('meta[name="color-scheme"]').content = localStorage.getItem("color-scheme") ?? "light dark";
</script>
- Esta secuencia de comandos intercalada existe solo para evitar el FOUC. Mantenlo lo más pequeño posible; el JS adicional para controlar el cambio de esquema de colores se puede cargar más tarde.
- En casos excepcionales, el acceso a
localStoragepuede generar un error (p.ej., datos del sitio bloqueados), pero como esta secuencia de comandos no hace nada más, el único efecto secundario sería un error de la consola. Si esto es importante, une entry .. catch. Asegúrate de que el tema se siga aplicando a la página, incluso si no se puede conservar. - La anulación puede cambiar desde otra pestaña: controla el evento
storagedewindowpara mantener la sincronización. - NO uses
matchMedia()para quitar el valor almacenado cuando la preferencia del sistema cambie para que coincida con él. Muchos SO de los usuarios cambian de esquema automáticamente según la hora del día. Si se quita el valor almacenado cada vez que coinciden, sería imposible fijar un esquema.
Ramificación para valores HTML y que no son de color
Los colores no necesitan trabajo adicional: una vez que <meta> se asigna a color-scheme (arriba), light-dark() sigue la anulación.
En esta sección, se explica cómo adaptar otros valores (p. ej., font-weight, fuentes de medios, etcétera).
Cuando la página simplemente sigue la preferencia del sistema, se puede usar (prefers-color-scheme: dark) para la ramificación, incluso en fuentes <picture> o <video>.
Anular el esquema de colores agrega una complicación adicional: la preferencia del sistema ya no es la fuente de información, y para las APIs que solo aceptan consultas de medios, no hay una alternativa directa.
NO confíes exclusivamente en las clases aplicadas con JS, como .dark, para la ramificación, ya que serán incorrectas si no se carga JS y el valor predeterminado del SO es oscuro.
Puedes combinar la consulta de medios con un selector :root:has(> head > meta[name="color-scheme"][content="dark"]) (o light) para crear una rama.
Consulta Combinaciones de selectores y reglas @ para obtener detalles sobre cómo implementar esto de forma elegante.
Las opciones del control también son ramas sin color: incluye ambos estados en el lenguaje de marcado (ícono más texto de acción oculto visualmente, p.ej., "Cambiar al tema oscuro") y oculta el inactivo con estos selectores.
display: none también quita la opción redundante de la TA, por lo que el control no necesita JS para mantenerse actualizado cuando cambia la preferencia del sistema.
En el caso del contenido multimedia en HTML, la única forma en este momento es incluir ambas versiones como elementos separados y activar o desactivar la visibilidad según corresponda.
Ocultar elementos <source> con display: none no funciona.
Interfaz de usuario
¿Dos o tres estados?
En todos los casos, la IU DEBE admitir la reversión o la configuración del esquema de colores al sistema.
Se recomienda mostrar dos estados en los botones de activación con espacio limitado, como los botones de activación persistentes en el marco del sitio (encabezado, navegación, pie de página, etc.), donde el espacio es limitado y ajustar la configuración suele ser secundario para el objetivo del usuario.
Un control de esquema de colores en una superficie de configuración independiente (página, panel o superposición) es una situación diferente y PUEDE exponer los tres estados de forma explícita ("Claro", "Oscuro", "Sistema"): allí, el usuario ya toma decisiones deliberadas sobre el comportamiento futuro, y hay espacio para explicar las opciones.
También se justifican tres estados explícitos si el sitio implementa esquemas adaptables al contexto (p.ej., un modo de luz más tenue cuando el SO está oscuro), ya que "Claro" y "Sistema (actualmente claro)" difieren genuinamente.
Botones de activación de dos estados
Cuando se usa un control de este tipo, suele ser un ajuste de comodidad temporal ("hay demasiada luz en este momento"), en lugar de garantizar que el estado actual se conserve a largo plazo. Por lo tanto, para los botones de activación de dos estados, basta con mostrar dos de los tres parámetros de configuración: el del sistema (para que se pueda revertir el parámetro de configuración) y el opuesto actual del sistema, almacenado como su valor literal actual.
Los únicos dos estados deberían ser los siguientes:
- Predeterminado del sistema: No hay un valor almacenado, el contenido de
<meta>eslight dark. Se muestra como su valor resuelto actual (p.ej., un ícono de sol cuando hay luz). - Anulación: Se almacena literalmente como
lightodark.
En esencia, es un control de tres estados (light dark, light y dark) en el que no se puede alcanzar el estado explícito que coincide con la preferencia actual del sistema.
Cuando el usuario activa o desactiva la opción, ocurre lo siguiente:
- Esquema de destino = lo opuesto al esquema renderizado actualmente (valor almacenado, si lo hay; de lo contrario, preferencia del sistema). La intención del usuario es "seleccionar lo opuesto a lo que veo ahora mismo", NO "seleccionar lo inverso al valor predeterminado del sistema".
- Si el objetivo difiere de la preferencia actual del sistema, almacénalo de forma literal.
- Si el objetivo coincide con la preferencia actual del sistema, el usuario está deshaciendo su ajuste: quita el valor almacenado. NO lo almacenes, ya que eso fijaría de forma invisible el esquema contra futuros cambios del sistema.
- Establece el contenido de
<meta>en el valor almacenado o enlight darksi no hay ninguno.
IMPORTANTE: La divergencia se debe verificar solo en el momento del almacenamiento, nunca de forma retroactiva: se DEBE conservar un valor almacenado que la preferencia del sistema cambie más adelante para que coincida. Muchos SO de los usuarios cambian de esquema automáticamente según la hora del día. Si se quita el valor almacenado cada vez que coinciden, sería imposible fijar un esquema.
Situación de ejemplo, que comienza con el SO configurado en claro:
- El usuario activa o desactiva la opción. El modo oscuro difiere de la preferencia del sistema, por lo que se almacena
darky el sitio se vuelve oscuro. - El parámetro de configuración del SO cambia a oscuro. El sitio permanece oscuro (el valor almacenado ahora coincide con la preferencia del sistema, pero se conserva).
- El parámetro de configuración del SO vuelve a ser claro. El sitio permanece oscuro.
- El usuario activa o desactiva la opción. El objetivo (claro) coincide con la preferencia del sistema, por lo que se quita el valor almacenado y el sitio vuelve a seguir el sistema.
Botones de activación de tres estados
Cuando hay más espacio y los usuarios ya tienen la mentalidad de establecer preferencias a largo plazo, un control de tres estados puede proporcionar más claridad. En este caso, el modelo de datos se asigna de forma clara a las tres opciones de la IU: sistema, claro y oscuro.
Para reducir la carga cognitiva, la opción "Sistema" también debe mostrar cuál es la configuración actual del sistema, por ejemplo, de la siguiente manera:
- Ícono, p.ej., un ícono de sol en la parte inferior derecha del ícono del sistema
- Texto, p.ej., "Sistema (actualmente claro)"