Construa um componente de troca de alternância do modo claro/escuro com CSS e JavaScript

Neste novo tutorial, usaremos o conhecimento prévio e aprenderemos como criar um componente de troca de alternância do modo claro/escuro. Este é um recurso útil disponível em muitos sites e aplicativos hoje em dia, pois pode ajudar a reduzir a tensão ocular e, portanto, melhora a acessibilidade.

Modos de cores do MDNModos de cores do MDNModos de cores do MDN
Modos de cores do MDN
Modos de cores do SlackModos de cores do SlackModos de cores do Slack
Modos de cores do Slack

Também vale a pena notar que mais e mais estruturas CSS começaram a fornecer essa funcionalidade por padrão.

Modo escuro do BootstrapModo escuro do BootstrapModo escuro do Bootstrap
Modos de cores da Bootstrap
Modo escuro de TailwindModo escuro de TailwindModo escuro de Tailwind
Modo escuro de Tailwind

O que estamos construindo

Aqui está Um vídeo introdutório para dar a você uma amostra da funcionalidade que queremos alcançar:

https://www.youtube.com/watch?v=y5ke6ul2q8k


E, claro, há o Demoção Codepen Para você bifurcar e brincar com:

Para acelerar o processo de desenvolvimento, usaremos fortemente a marcação e os estilos de um tutorial anterior. Nesse tutorial, desenvolvemos um componente de troca de alternância usando a técnica de hack de caixa de seleção CSS da velha escola.

1. Comece com a marcação de página

Nossa página suportará modos claros e escuros. Por padrão, o modo de luz estará ativo.

Em relação à marcação, precisaremos de três botões de rádio no total. Quando o terceiro botão de rádio (Auto) está ativo, as cores da página dependerão do esquema de cores definido em nossas configurações do sistema operacional.

Nosso componente de alternância do modo claro/escuroNosso componente de alternância do modo claro/escuroNosso componente de alternância do modo claro/escuro

Configurando o modo escuro no Windows 11 Configurando o modo escuro no Windows 11 Configurando o modo escuro no Windows 11
Configurando o modo escuro no Windows 11

Aqui está a estrutura necessária:

1
 class="switches">
2
  
  • 3
         type="radio" id="light" name="theme-mode" checked>
    
    4
         for="light">
    
    5
          Light
    
    6
          
    
    7
        
    
    8
      
    
    9
      
  • 10
         type="radio" id="dark" name="theme-mode">
    
    11
         for="dark">
    
    12
          Dark
    
    13
          
    
    14
        
    
    15
      
    
    16
      
  • 17
         type="radio" id="auto" name="theme-mode">
    
    18
         for="auto">
    
    19
          System
    
    20
          
    
    21
        
    
    22
      
    
    23
    
    

    2. Adicione o CSS

    Definiremos as cores iniciais da página usando variáveis ​​CSS.

    1
    :root {
    
    2
      ...
    
    3
      --white: #fff;
    
    4
      --black: black;
    
    5
      --text-color: var(--black);
    
    6
      --bg-color: var(--white);
    
    7
      ...
    
    8
    }
    

    Então, assim que o modo escuro for ativado, substituiremos os valores dessas variáveis.

    1
    .theme-dark {
    
    2
      color-scheme: dark;
    
    3
      --text-color: #fff;
    
    4
      --bg-color: black;
    
    5
      ...
    
    6
    }
    

    Observe o color-scheme: dark Valor da propriedade que permite aos navegadores renderizar elementos nativos no modo escuro (por exemplo, controles de formulário).

    Claro, podemos ir ainda mais longe daqui. Por exemplo, podemos ter diferentes filtros de imagem, dependendo do modo ativo, etc.

    Abaixo, você pode ver os estilos responsáveis ​​por criar a aparência dos nossos interruptores de alternância.

    1
    /*CUSTOM VARIABLES HERE*/
    
    2
    
    
    3
    .switches {
    
    4
      display: inline-block;
    
    5
      padding: 0;
    
    6
      border: 1px solid var(--gray);
    
    7
      margin: 10px 0 0;
    
    8
      border-radius: 6px;
    
    9
    }
    
    10
    
    
    11
    .switches li {
    
    12
      position: relative;
    
    13
    }
    
    14
    
    
    15
    .switches li:not(:last-child) {
    
    16
      border-bottom: 1px solid var(--gray);
    
    17
    }
    
    18
    
    
    19
    .switches li [type="radio"] {
    
    20
      position: absolute;
    
    21
      left: -9999px;
    
    22
    }
    
    23
    
    
    24
    .switches label {
    
    25
      display: grid;
    
    26
      grid-template-columns: 40px auto;
    
    27
      align-items: center;
    
    28
      gap: 10px;
    
    29
      padding: 20px;
    
    30
      cursor: pointer;
    
    31
    }
    
    32
    
    
    33
    .switches span {
    
    34
      flex-shrink: 0;
    
    35
    }
    
    36
    
    
    37
    .switches span:empty {
    
    38
      position: relative;
    
    39
      width: 50px;
    
    40
      height: 26px;
    
    41
      border-radius: 15px;
    
    42
      background: var(--gray);
    
    43
      transition: all 0.3s;
    
    44
    }
    
    45
    
    
    46
    .switches span:empty::before,
    
    47
    .switches span:empty::after {
    
    48
      content: "";
    
    49
      position: absolute;
    
    50
    }
    
    51
    
    
    52
    .switches span:empty::before {
    
    53
      top: 1px;
    
    54
      left: 1px;
    
    55
      width: 24px;
    
    56
      height: 24px;
    
    57
      background: var(--slate-gray);
    
    58
      border-radius: 50%;
    
    59
      z-index: 1;
    
    60
      transition: transform 0.3s;
    
    61
    }
    
    62
    
    
    63
    .switches span:empty::after {
    
    64
      top: 50%;
    
    65
      transform: translateY(-50%);
    
    66
      width: 14px;
    
    67
      height: 14px;
    
    68
      left: 6px;
    
    69
      background-size: 14px 14px;
    
    70
    }
    
    71
    
    
    72
    .switches [type="radio"]:checked + label span:empty {
    
    73
      background: var(--white-pearl);
    
    74
    }
    
    75
    
    
    76
    .switches [type="radio"]:checked + label span:empty::before {
    
    77
      transform: translateX(24px);
    
    78
    }
    
    79
    
    
    80
    .switches li:nth-child(1) [type="radio"]:checked + label span:empty::after {
    
    81
      background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM2MS41IDEuMmM1IDIuMSA4LjYgNi42IDkuNiAxMS45TDM5MSAxMjFsMTA3LjkgMTkuOGM1LjMgMSA5LjggNC42IDExLjkgOS42czEuNSAxMC43LTEuNiAxNS4yTDQ0Ni45IDI1Nmw2Mi4zIDkwLjNjMy4xIDQuNSAzLjcgMTAuMiAxLjYgMTUuMnMtNi42IDguNi0xMS45IDkuNkwzOTEgMzkxIDM3MS4xIDQ5OC45Yy0xIDUuMy00LjYgOS44LTkuNiAxMS45cy0xMC43IDEuNS0xNS4yLTEuNkwyNTYgNDQ2LjlsLTkwLjMgNjIuM2MtNC41IDMuMS0xMC4yIDMuNy0xNS4yIDEuNnMtOC42LTYuNi05LjYtMTEuOUwxMjEgMzkxIDEzLjEgMzcxLjFjLTUuMy0xLTkuOC00LjYtMTEuOS05LjZzLTEuNS0xMC43IDEuNi0xNS4yTDY1LjEgMjU2IDIuOCAxNjUuN2MtMy4xLTQuNS0zLjctMTAuMi0xLjYtMTUuMnM2LjYtOC42IDExLjktOS42TDEyMSAxMjEgMTQwLjkgMTMuMWMxLTUuMyA0LjYtOS44IDkuNi0xMS45czEwLjctMS41IDE1LjIgMS42TDI1NiA2NS4xIDM0Ni4zIDIuOGM0LjUtMy4xIDEwLjItMy43IDE1LjItMS42ek0xNjAgMjU2YTk2IDk2IDAgMSAxIDE5MiAwIDk2IDk2IDAgMSAxIC0xOTIgMHptMjI0IDBhMTI4IDEyOCAwIDEgMCAtMjU2IDAgMTI4IDEyOCAwIDEgMCAyNTYgMHoiLz48L3N2Zz4=");
    
    82
    }
    
    83
    
    
    84
    .switches li:nth-child(2) [type="radio"]:checked + label span:empty::after {
    
    85
      background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzODQgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTIyMy41IDMyQzEwMCAzMiAwIDEzMi4zIDAgMjU2UzEwMCA0ODAgMjIzLjUgNDgwYzYwLjYgMCAxMTUuNS0yNC4yIDE1NS44LTYzLjRjNS00LjkgNi4zLTEyLjUgMy4xLTE4LjdzLTEwLjEtOS43LTE3LTguNWMtOS44IDEuNy0xOS44IDIuNi0zMC4xIDIuNmMtOTYuOSAwLTE3NS41LTc4LjgtMTc1LjUtMTc2YzAtNjUuOCAzNi0xMjMuMSA4OS4zLTE1My4zYzYuMS0zLjUgOS4yLTEwLjUgNy43LTE3LjNzLTcuMy0xMS45LTE0LjMtMTIuNWMtNi4zLS41LTEyLjYtLjgtMTktLjh6Ii8+PC9zdmc+");
    
    86
    }
    
    87
    
    
    88
    .switches li:nth-child(3) [type="radio"]:checked + label span:empty::after {
    
    89
      background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTQ0OCAyNTZjMC0xMDYtODYtMTkyLTE5Mi0xOTJsMCAzODRjMTA2IDAgMTkyLTg2IDE5Mi0xOTJ6TTAgMjU2YTI1NiAyNTYgMCAxIDEgNTEyIDBBMjU2IDI1NiAwIDEgMSAwIDI1NnoiLz48L3N2Zz4=");
    
    90
    }
    

    3. Adicione o JavaScript

    Para configurar a funcionalidade do nosso componente de troca de alternância, aproveitaremos o armazenamento local e o prefers-color-scheme Recurso de mídia CSS. Essa consulta de mídia útil leva como valor a seleção preferida do usuário, conforme definido em suas configurações do sistema operacional.

    A presença do theme-dark classe no html O elemento indicará que solicitamos o modo escuro manualmente ou através das configurações do sistema.

    O modo escuro habilitadoO modo escuro habilitadoO modo escuro habilitado

    Nesse ponto, armazenaremos em armazenamento local, o dark-mode="true" Valor da chave que nos ajudará a manter a seleção do esquema de cores do usuário na página Reload.

    O valor armazenado no armazenamento localO valor armazenado no armazenamento localO valor armazenado no armazenamento local

    Também armazenamos um segundo valor de chave para identificar a chave de alternância ativa.

    Armazene em armazenamento local o botão de rádio ativoArmazene em armazenamento local o botão de rádio ativoArmazene em armazenamento local o botão de rádio ativo

    Com tudo isso em mente, aqui está todo o JavaScript necessário – use seu navegador para verificar como funciona:

    1
    const html = document.documentElement;
    
    2
    const switches = document.querySelector(".switches");
    
    3
    const inputs = switches.querySelectorAll("input");
    
    4
    
    
    5
    if (localStorage.getItem("dark-mode")) {
    
    6
      html.classList.add("theme-dark");
    
    7
    }
    
    8
    
    
    9
    if (localStorage.getItem("selected-radio")) {
    
    10
      switches.querySelector(`#${localStorage.getItem("selected-radio")}`).checked =
    
    11
        "true";
    
    12
    }
    
    13
    
    
    14
    const setTheme = (theme) => {
    
    15
      if (theme === "dark") {
    
    16
        html.classList.add("theme-dark");
    
    17
        localStorage.setItem("dark-mode", "true");
    
    18
      } else {
    
    19
        html.classList.remove("theme-dark");
    
    20
        localStorage.removeItem("dark-mode");
    
    21
      }
    
    22
    };
    
    23
    
    
    24
    const handleMediaChange = (e) => {
    
    25
      if (switches.querySelector('[type="radio"]:checked').id === "auto") {
    
    26
        setTheme(e.matches ? "dark" : "light");
    
    27
      }
    
    28
    };
    
    29
    
    
    30
    const handleInputChange = (e) => {
    
    31
      const themeMode = e.target.id;
    
    32
      if (
    
    33
        themeMode === "dark" ||
    
    34
        (themeMode === "auto" &&
    
    35
          window.matchMedia("(prefers-color-scheme: dark)").matches)
    
    36
      ) {
    
    37
        setTheme("dark");
    
    38
      } else {
    
    39
        setTheme("light");
    
    40
      }
    
    41
      localStorage.setItem("selected-radio", themeMode);
    
    42
    };
    
    43
    
    
    44
    window
    
    45
      .matchMedia("(prefers-color-scheme: dark)")
    
    46
      .addEventListener("change", handleMediaChange);
    
    47
    
    
    48
    inputs.forEach((input) => input.addEventListener("input", handleInputChange));
    

    Conclusão

    Feito! Espero que você tenha se divertido com este exercício e considere esse componente sempre que precisar de uma funcionalidade de alternância do tema claro/escuro.

    Vamos nos lembrar de nossa criação:

    Como sempre, muito obrigado pela leitura!

    Mais tutoriais de modos de cores

    Precisa de mais tutoriais do esquema de cores? Confira os tutoriais abaixo:

    Deixe uma resposta