Agregar un botón de modo oscuro con MaterializeCSS 2

Oct 25, 2024   HIRANO Satoshi

Esta guía explica cómo implementar un botón de modo oscuro con detección del tema del sistema operativo usando MaterializeCSS 2.1. MaterializeCSS ha estado avanzando hacia el soporte de Material Design M3 desde la versión 2.

dev CSS Material Design Materialize Aurelia
Síganos
Compartir

Vamos a crear un conjunto de botones como este. El botón central permite detectar y seguir la configuración de tema del sistema operativo.




La documentación de temas de MaterializeCSS explica cómo crear un botón de alternancia claro/oscuro, pero ¿qué pasa con detectar y seguir la configuración de tema del sistema? Aquí te mostramos cómo lograrlo.


Implementación principal

Gracias al buen diseño de MaterializeCSS, el código central es sorprendentemente simple. Este fragmento de código agrega un atributo theme="light" o theme="dark" a la etiqueta <html>, y si el usuario selecciona el modo del sistema operativo, elimina el atributo:


if (mode === 'dark' || mode === 'light') {
    document.documentElement.setAttribute('theme', mode);
} else if (mode === 'os') {
    document.documentElement.removeAttribute('theme');
}

¿Por qué funciona este código?

La razón por la que este código funciona radica en las variables CSS y las consultas de medios (media queries), que se adaptan automáticamente al tema elegido. A continuación, hay un fragmento de theme.module.scss donde se configuran las variables CSS para los modos claro y oscuro:


:root {
  color-scheme: light;
  --md-sys-color-surface: var(--md-sys-color-surface-light);
  --md-sys-color-on-surface: var(--md-sys-color-on-surface-light);
}
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --md-sys-color-surface: var(--md-sys-color-surface-dark);
    --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark);
  }
}
:root[theme='light'] {
  color-scheme: light;
  --md-sys-color-surface: var(--md-sys-color-surface-light);
  --md-sys-color-on-surface: var(--md-sys-color-on-surface-light);
}
:root[theme='dark'] {
  color-scheme: dark;
  --md-sys-color-surface: var(--md-sys-color-surface-dark);
  --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark);
}

La variable --md-sys-color-surface representa el color de fondo, que es blanco (#fcfcff) en el modo claro y negro (#1a1c1e) en el modo oscuro. Al alternar el atributo theme, el navegador selecciona los colores correctos según el tema.


Agregar color-scheme a CSS

Para admitir completamente ambos modos (claro y oscuro), debes agregar color-scheme: light dark; a tu CSS:


:root {
    color-scheme: light dark;
}

Esto le indica al navegador que aplique automáticamente el esquema de color adecuado según la configuración del usuario o las preferencias del sistema operativo.


Cómo se aplican los temas en diferentes situaciones

A continuación se muestra cómo este conjunto de configuraciones se comporta en diferentes escenarios:


  • Si la etiqueta <html> tiene theme="light", se usan las variables CSS en :root[theme='light'].
  • Si se configura theme="dark", se aplican las variables CSS en :root[theme='dark'].
  • Si no hay un atributo theme y el sistema operativo está en modo oscuro, se activa el CSS bajo @media (prefers-color-scheme: dark).
  • Si el sistema operativo está en modo claro y no hay atributo theme, se aplica el CSS por defecto en :root.

Ejemplo completo de código

Aquí tienes el código completo. Utiliza Aurelia (1) como framework, pero puedes adaptarlo a cualquier configuración de JavaScript. El modo seleccionado se guarda en localStorage, lo que permite restaurarlo con restoreDarkMode() cuando la aplicación se inicia:



- app.html <style> :root { color-scheme: light dark; } </style> <require from="../resources/dark-mode"></require> <dark-mode></dark-mode>


- dark-mode.html

<template>
  <style>
    .active-shade .active {
      background-color: rgba(128, 128, 128, 0.2);
    }
  </style>

  <div class="center active-shade">
    <button md-button="flat: true;" click.delegate="setDarkMode('light')" class="${mode=='light' ? 'active' : ''}" title="${'dark.light' & t}">
      <i class="material-icons-outlined">light_mode</i>
    </button>

    <button md-button="flat: true;" click.delegate="setDarkMode('os')" class="${mode=='os' ? 'active' : ''}" title="${'dark.os' & t}">
      <i class="material-icons-outlined">computer</i>
    </button>

    <button md-button="flat: true;" click.delegate="setDarkMode('dark')" class="${mode=='dark' ? 'active' : ''}" title="${'dark.dark' & t}">
      <i class="material-icons-outlined">dark_mode</i>
    </button>
  </div>
</template>
- dark-mode.ts

type DarkModeType = 'light'|'os'|'dark';

/**
  Manages dark mode.
 */
export class DarkMode {
    mode: DarkModeType;

    constructor() {
        this.mode = getDarkMode();
    }

    /**
      Sets light/dark mode.

      @params mode Any of DarkModeType. If mode is not right, 'light' is used.
     */
    setDarkMode(mode: DarkModeType) {
        this.mode = setDarkMode(mode);
    }
}

/**
  Gets light/dark mode.
 */
export function getDarkMode(): DarkModeType {
    return localStorage.darkMode || 'light';
}

/**
  Sets light/dark mode.

  @params mode Any of DarkModeType. If mode is not right, 'light' is used.
  @returns A canonical mode.
 */
export function setDarkMode(mode: DarkModeType): DarkModeType {
    if (!['light', 'os', 'dark'].includes(mode)) mode = 'light';

    if (mode === 'dark' || mode === 'light') {
        document.documentElement.setAttribute('theme', mode);
    } else if (mode === 'os') {
        document.documentElement.removeAttribute('theme');
    }

    localStorage.darkMode = mode;
    return mode;
}

/**
  Restores light/dark mode from localStorage.
 */
export function restoreDarkMode() {
    setDarkMode(getDarkMode());
}

Si necesitamos cambiar los colores de una clase entre el modo claro y el modo oscuro, se requiere un conjunto de clases como este.

  /* for explicit light mode */
  .shaded {
      background-color: rgba(128, 128, 128, 0.15) !important;
  }

  /* for explicit dark mode */
  html[theme="dark"] .shaded {
      background-color: rgba(128, 128, 128, 0.35) !important;
  }

  /* for OS dark mode and not explicit light mode */
  @media (prefers-color-scheme: dark) {
      html:not(html[theme="light"]) .shaded,
          background-color: rgba(128, 128, 128, 0.35) !important;
      }
  }