Criando um tema escuro com JS e CSS
Tags: javascript, webdev
Published: 2022-07-04 17:33:25

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](https://stackoverflow.blog/2020/03/31/building-dark-mode-on-stack-overflow/) lançou seu tema escuro no começo deste ano, e mais recentemente foi a vez do [GitHub](https://github.blog/2020-12-08-new-from-universe-2020-dark-mode-github-sponsors-for-companies-and-more/) fazer o mesmo. Até a [Google](https://9to5google.com/2020/12/12/google-search-dark-mode-web/) 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!

<!--more-->

# 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._

```css
: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`:

```css
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_

```css
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.

```css
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.

```html
<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`_

```js
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.

```js
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.

```js
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 no`localStorage`.

```js
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.

```js
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.

```js
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/