





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






O que estamos construindo
Aqui está Um vídeo introdutório para dar a você uma amostra da funcionalidade que queremos alcançar:
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.



Aqui está a estrutura necessária:
1 |
|
2 |
|
3 |
type="radio" id="light" name="theme-mode" checked>
|
4 |
|
5 |
Light
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
type="radio" id="dark" name="theme-mode">
|
11 |
|
12 |
Dark
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
type="radio" id="auto" name="theme-mode">
|
18 |
|
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.



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.



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



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: