Criando um tema escuro com JS e CSS


Ultimamente tem sido comum que vários sites, principalmente os relacionados a tecnologia, que antes possuíam apenas uma tema padrão, de cores claras, e que agora tem criado a possibilidade do usuário utilizar temas de cores escuras.
O StackOverflow lançou seu tema escuro no começo deste ano, e mais recentemente foi a vez do GitHub fazer o mesmo. Até a Google está testando um tema escuro para o seu buscador nos desktops.

Se você não tem tanta familiaridade com o desenvolvimento web, pode acabar pensando que é muito complexo criar um mecanismo de temas para manter um tema claro e um escuro da mesma forma que esses sites fazem.

Porém isso não é verdade! É possível criar um mecanismo de temas apenas utilizando JavaScript e CSS3 puros, sem nenhum outro framework ou ferramenta, e neste artigo eu vou mostrar como isso pode ser feito!

Variáveis, variáveis por todos os lados

Para começar, primeiro você deve entender exatamente o que vamos utilizar para criar o tema.
Primeiro, da parte de JavaScript, não temos muitos segredos, vamos apenas manipular alguns atributos para alterar o tema, e também iremos salvar a seleção do usuário no navegador, já vamos chegar nisso.

A mágica toda acontece com o CSS3, utilizando de suas variáveis.
As variáveis no CSS foram introduzidas na linguagem e podem ser usadas praticamente desde 2012, porém só a partir de 2015 que elas começaram a ser suportadas com maior abrangência pelos navegadores, e hoje praticamente só não é suportado pelo Internet Explorer (nem a Microsoft dá mais suporte para o IE…).

Como essas variáveis funcionam?

Em um arquivo CSS, as variáveis funcionam com um par de chave/valor, bem parecido como as propriedades dentro da regra de um seletor.
A diferença é que você pode definir qualquer nome para uma variável, e ela deve iniciar com dois traços.
Depois, para utilizar de fato a variável, basta utilizar var() com o nome que foi definido.
Por exemplo, no código abaixo, é possível ver a utilização das variáveis para guardar valores de cores, tamanhos e medidas, etc. Qualquer valor que pode ser definido a uma propriedade, pode ser armazenada em uma variável, e depois elas podem ser reaproveitadas onde for necessário.

Você pode declarar as variáveis dentro de um seletor :root ou html, e assim elas estão disponíveis para serem utilizadas nas em todas as regras que você definir posteriormente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:root {
--fonte-padrao: 12px;
--fonte-pequena: 8px;
--fonte-grande: 16px;
--cor-da-fonte: #12171a;
--cor-de-alerta: #f10505;
}

.alerta {
background-color: var(--cor-de-alerta);
font-size: var(--fonte-grande);
}

#botao-enviar {
color: var(--cor-da-fonte);
font-size: var(--fonte-pequena);
}

Mesmo sem levar em consideração a questão da criação de temas, o uso de variáveis no seu CSS já pode ser de grande valor. Centralizar em apenas um local os valores de cores e medidas diversas utilizadas deixa o código muito mais organizado e facilita a manutenção.

Agora que você já sabe o que são e como as variáveis do CSS podem ser utilizadas, vamos à criação dos nossos temas!

Come To The Dark Side

Para criação do nosso tema, vamos considerar que temos um site simples, composto de um index.html para a página, um main.js para o código JavaScript, e um style.css para definir o estilo dessa nossa página.

Por exemplo, vamos imaginar que tenhamos essas regras no nosso style.css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
body {
background-color: #f5f5f5;
margin: 0;
font-family: "Roboto", sans-serif;
color: #000000;
}

a {
color: #0000ee;
}

.header-title {
color: #333333;
}

.header-subtitle {
color: #969696;
}

.page-header {
padding: 15px;
background-color: #e3e3e3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
text-align: center;
}

A primeira coisa que devemos fazer é pegar cada uma dessas cores que estão definidas entre as diversas regras do arquivo, transformar em variáveis, e adicionar em uma regra genérica para todo a tag html da página.
Lembrando que você também pode fazer o mesmo com o valor de outras propriedades das regras, com as margens, espaçamentos, etc. Mas como o foco aqui é a criação do tema, vamos fazer isso apenas com as cores

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
html {
--background-color: #f5f5f5;
--header-background-color: #e3e3e3;
--header-title-color: #333333;
--header-subtitle-color: #969696;
--link-color: #0000ee;
--text-color: #000000;
}

body {
background-color: var(--background-color);
margin: 0;
font-family: "Roboto", sans-serif;
color: var(--text-color);
}

a {
color: var(--link-color);
}

.header-title {
color: var(--header-title-color);
}

.header-subtitle {
color: var(--header-subtitle-color);
}

.page-header {
padding: 15px;
background-color: var(--header-background-color);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
text-align: center;
}

Só de fazer essa alteração você já deixou seu CSS mais organizado, mantendo a página visualmente sem nenhuma alteração.

Agora o que nós precisamos fazer, é criar uma segunda regra com o conjunto de cores que serão utilizados no tema oposto. No caso, essas cores são do tema claro, então precisamos criar as mesmas variáveis para quando formos utilizar o tema escuro.

E para diferenciar entre os dois conjuntos de variáveis, em vez de manter as regras apenas para o seletor da tag html, vamos definir também em conjunto uma propriedade data-theme, que vai receber ou o valor light quando for o tema claro, ou dark quando for o tema escuro.
Veremos o uso dessa propriedade mais a frente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
html[data-theme="light"] {
--background-color: #f5f5f5;
--header-background-color: #e3e3e3;
--header-title-color: #333333;
--header-subtitle-color: #969696;
--link-color: #0000ee;
--divider-color: #006064;
--text-color: #000000;
}

html[data-theme="dark"] {
--background-color: #171e22;
--header-background-color: #1c262b;
--header-title-color: #5a7788;
--header-subtitle-color: #35454f;
--link-color: #7d95ff;
--divider-color: #006435;
--text-color: #acacac;
}

body {
background-color: var(--background-color);
margin: 0;
font-family: "Roboto", sans-serif;
color: var(--text-color);
}

a {
color: var(--link-color);
}

.header-title {
color: var(--header-title-color);
}

.header-subtitle {
color: var(--header-subtitle-color);
}

.page-header {
padding: 15px;
background-color: var(--header-background-color);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
text-align: center;
}

Agora, no nosso arquivo HTML, precisamos definir qual tema será utilizado por padrão, adicionando a propriedade data-theme para a tag html da página, com o valor desejado.

1
<html lang="en" data-theme="light"></html>

Só com isso você já vai conseguir ver o seu site alternando entre os dois temas criados, basta alterar o valor da propriedade data-theme entre dark e light e recarregar a página que “magicamente” as cores serão alteradas!

Agora é ligar os pontos

Agora que nós temos as cores todas definidas como variáveis, e também já temos as cores do nossos dois temas, vamos precisar de um pouco de JavaScript para criar uma função que troque o valor do atributo data-theme dinamicamente, permitindo que o usuário escolha o tema que achar mais agradável.

Na interface do usuário, isso pode ser feito de diversas maneiras: com uma caixa de seleção que liste os temas, um checkbox de ativação do tema, um botão que alterna entre claro e escuro, etc. Mas independente da maneira que for, o que deve ser feito internamente é definir na propriedade data-theme o valor para o tema desejado.

Por exemplo, podemos criar uma função no JavaScript que, caso o tema atual seja o claro, ele alterne para o escuro, e vice-versa. E podemos vincular essa função no evento de clique de um botão que exista na página.

Toda vez que esse botão for clicado, a função irá obter o valor atual da propriedade data-theme, e irá trocar para o valor inverso, e as cores serão alteradas.
É importante que apenas um elemento na página possua essa propriedade data-theme, que usualmente é a tag html ou a body

1
2
3
4
5
6
7
8
9
10
11
let themeToggler = document.getElementById("theme-toggler");
themeToggler.addEventListener("click", () => {
let targetTheme;
let currentTheme = document.documentElement.getAttribute("data-theme");
if (currentTheme === "light") {
targetTheme = "dark";
} else {
targetTheme = "light";
}
document.documentElement.setAttribute("data-theme", targetTheme);
});

Refletindo o tema do navegador

Todos os navegadores modernos, como o Chrome, Firefox e o Edge (o novo Edge, não o antigo) também possuem temas claros e escuros neles próprios, e é possível adicionar na nossa página uma função JavaScript que irá tentar obter essa configuração, e a sua página automaticamente ficará com o mesmo tema do navegador.
Essa função pode ser executada cada vez que a página é carregada, e deve buscar pela configuração prefers-color-scheme no navegador.
Se essa configuração existir e o valor for dark, definimos esse mesmo valor no nosso atributo data-theme.
Agora, se a configuração for qualquer outra, ou se por um acaso o navegador não tiver suporte para ela e ela não existir, definimos nosso tema como light, que é nosso tema padrão.

1
2
3
4
let browserTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
document.documentElement.setAttribute("data-theme", browserTheme);

Caso você deseje manter o tema escuro como padrão, é só inverter a verificação.

1
2
3
let browserTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";

Salvando a seleção do usuário

Podemos também criar uma forma simples de salvar o tema selecionado pelo usuário, mantendo essa informação no localStorage do navegador.
Desta forma você pode manter entre as seções do usuário o tema de preferência dele.

Para isso, na função de troca do tema, sempre que você alterar o atributo data-theme, você pode também salvar o valor nolocalStorage.

1
localStorage.setItem("theme", targetTheme);

Com essa informação salva, você pode fazer a chamada para uma outra função no carregamento da página para verificar se existe esse valor armazenado naquele navegador, e se existir, utilizá-lo. Caso contrário, fica definido o tema padrão.

1
2
3
4
let savedTheme = localStorage.getItem("theme");
if (savedTheme) {
document.documentElement.setAttribute("data-theme", savedTheme);
}

É possível até juntar na lógica a verificação tanto do tema salvo pelo usuário, quanto fazer refletir o tema do navegador.

Se existir um tema salvo, utiliza este. Caso contrário, tenta obter o tema configurado no navegador. Se o navegador não suportar, vai ficar definido o tema padrão.

1
2
3
4
5
6
7
8
9
let savedTheme = localStorage.getItem("theme");
if (savedTheme) {
document.documentElement.setAttribute("data-theme", savedTheme);
} else {
let browserTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
document.documentElement.setAttribute("data-theme", browserTheme);
}

Não se limite aos temas

Com esse mecanismo é possível criar quantos temas forem necessários, basta criar o conjunto de variáveis com os valores das cores de cada tema, e fazer as funções que atualizem o atributo data-theme.

E também, é claro, as variáveis no CSS são excelentes para deixar o código mais organizado e centralizar as definições em apenas em um local mesmo que você não queira criar nenhum tema, funcionando muito bem também com frameworks de SPA, como o React e o Angular por exemplo, garantindo que todos os componentes sigam os mesmos padrões.

Caso deseje ver um exemplo de implementação desse mecanismo de temas, você pode consultar este repositório do GitHub: https://github.com/lfrigodesouza/dark-theme-with-css
Também pode ver ele em funcionamento nesta página: https://lfrigodesouza.github.io/dark-theme-with-css/


Compartilhe: Twitter - Facebook - LinkedIn