# Dark Mode com variáveis de luminosidade invertida

> Técnica avançada de dark mode usando variáveis de luminosidade invertida. Implemente modo escuro com LCH e variáveis CSS de forma inovadora.

URL: https://dpw.dev/css/dark-mode-lch/  
Publicado em: 2021-04-15  
Categoria: css  
Tags: interface  
Autor: Tárcio Zemel

Já mostramos [o jeito mais fácil de fazer dark mode com CSS](/css/dark-mode-css-facil/), uma técnica que usa variáveis CSS para armazenar valores de cores e faz a diferenciação entre dark mode e light mode através de uma classe.

Neste artigo, abordamos uma técnica interessantíssima para fazer **Dark Mode com variáveis de luminosidade invertidas**, algo tão incrível, mas tão recente, que nem todos os navegadores dão suporte (ainda), mas que, em breve, pode se tornar a maneira padrão para implementar dark mode em qualquer projeto Web.

É possível usar [variáveis CSS](/css/variaveis-css-guia-pratico/) para componentes de cores individuais para evitar a repetição das mesmas coordenadas de cores várias vezes ao longo de um tema.

Dá até para usar a mesma variável para vários componentes, por exemplo, com HSL Hue e Lightness:

```scss
:root {
  --primary-hs: 250 30%;
}

h1 {
  color: hsl(var(--primary-hs) 30%);
}

article {
  background: hsl(var(--primary-hs) 90%);
}

article h2 {
  background: hsl(var(--primary-hs) 40%);
  color: white;
}
```

Aqui está uma página simples feita com essa técnica:

[Exemplo no CodePen](https://codepen.io/anon/pen/QWdjpEL)

Ao contrário das variáveis de pré-processadores, é possível até mesmo substituir localmente a variável para ter blocos com uma cor de destaque diferente:

```scss
:root {
  --primary-hs: 250 30%;
  --secondary-hs: 190 40%;
}

article {
  background: hsl(var(--primary-hs) 90%);
}

article.alt {
  --primary-hs: var(--secondary-hs);
}
```

Como pode ser visto neste exemplo:

[Exemplo no CodePen](https://codepen.io/anon/pen/rNjOypm)

## Dark Mode entra em jogo

Tudo isso é ótimo e elegante, até que **dark mode** entre em jogo.

A ideia de usar propriedades personalizadas para facilitar a adaptação de um tema ao modo escuro não é nova. No entanto, na maioria dos artigos pelas webs, a estratégia sugerida é criar várias propriedades personalizadas, uma para cada cor, e substituí-las através de media queries.

Não deixa de ser uma abordagem interessante, mas, mesmo nos designs mais disciplinados, nem todas as cores são variáveis CSS.

Freqüentemente, encontram-se cores declaradas inline. Isso significa que lidar com dark mode é desgastante o suficiente para que a maioria simplesmente deixe para depois...

A boa notícia é que isso pode ser mitigado ao **usar Dark Mode com variáveis de luminosidade invertidas (HSL)**!

Saiba, de antemão, que não é um processo ótimo, e você deve, eventualmente, ajustá-lo para criar um modo escuro adequado -- já que f[azer um tema escuro não se trata apenas de trocar cores](https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/) --, mas é melhor do que nada e pode servir como uma excelente base.

## Explicando a técnica

A ideia-base é usar propriedades personalizadas (variáveis CSS) para a luminosidade (_lightness_) das cores, ao invés da cor inteira. Então, no dark mode, substituir essas variáveis com `100% - lightness`.

Geralmente, essa técnica produz cores claras para cores escuras, cores médias para cores médias e cores escuras para cores claras e ainda permite definir cores embutidas, em vez de forçar o uso de uma variável para cada cor.

O código do exemplo:

```scss
:root {
  --primary-hs: 250 30%;
  --secondary-hs: 190 40%;

  --l-0: 0%;
  --l-30: 30%;
  --l-40: 40%;
  --l-50: 50%;
  --l-90: 90%;
  --l-100: 100%;
}

@media (prefers-color-scheme: dark) {
  :root {
    --l-0: 100%;
    --l-30: 70%;
    --l-40: 60%;
    --l-90: 10%;
    --l-100: 0%;
  }
}

body {
  background: hsl(0 0% var(--l-100));
  color: hsl(0 0% var(--l-0));
}

h1 {
  color: hsl(var(--primary-hs) var(--l-30));
}

article {
  background: hsl(var(--primary-hs) var(--l-90));
}

article h2 {
  background: hsl(var(--primary-hs) 40%);
  color: white;
}

footer {
  color: hsl(0 0% var(--l-40));
}
```

A diferença entre o modo claro e escuro fica assim:

<figure>

![Dark Mode: diferença entre modo claro e modo escuro usando variáveis de cor HSL.](/images/blog/dark-mode-lch/image-1024x381.webp)

<figcaption>

Light mode e dark mode, gerados "automaticamente", lado-a-lado.

</figcaption>

</figure>

Observe que toda a informação sobre lightnesses foi substituída por variáveis de luminosidade -- o que, na verdade, não precisa ser obrigatório e tão abrangente.

Por exemplo, os títulos dos artigos teriam uma aparência melhor e teriam melhor contraste se simplesmente fossem mantidos iguais:

<figure>

![Dark Mode: diferença entre modo claro e modo escuro com pequenas adaptações.](/images/blog/dark-mode-lch/image1-1024x373.webp)

<figcaption>

Comparação do modo escuro com cada claridade se tornando uma variável versus uma abordagem mais refinada, com exceções feitas conforme necessário (neste caso, as cores de fundo e de texto para `article > h2`).

</figcaption>

</figure>

Essas são decisões fáceis de tomar enquanto se analisa o CSS, substituindo as porcentagens de luminosidade por variáveis e visualizando o resultado.

## O problema com HSL

Mas _por que_ os cabeçalhos dos artigos foram mais fáceis de ler com suas cores originais do que com a claridade invertida? A principal causa é que **a Luminosidade de HSL não corresponde realmente ao que os nós, humanos, percebemos como luminosidade**, e essa diferença pode produzir discrepâncias perceptivas acentuadas.

Esse é o grande problema com esta abordagem: ela assume que a luminosidade de HSL realmente significa algo, mas, [como discutido neste artigo](https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#3-lch-lightness-actually-means-something), não significa. Por exemplo, amarelo e azul têm a mesma luminosidade HSL de 50%... Além disso, nota-se que as cores escuras têm diferenças menores entre elas do que as cores claras, porque o HSL não é perceptivelmente uniforme.

Isso significa, então, que a técnica de Dark Mode com variáveis de luminosidade invertidas não é útil para nada além de um placeholder enquanto se desenvolve um dark mode real?

A resposta é: não. :)

Em breve, será possível **trabalhar com cores LCH diretamente nos navegadores**. [A primeira implementação foi lançada recentemente no Safari](https://webkit.org/blog/11548/release-notes-for-safari-technology-preview-120/#post-11548:~:text=Added%20support%20for%20lab\(\)%2C%20lch\(\)%2C%20and%20color\(lab%20...\)%20colors) e também há atividade nesse sentido entre os outros vendors.

**LCH** é um espaço de cores muito melhor para essa técnica, já que sua luminosidade na verdade _significa_ algo, não apenas em diferentes tonalidades da mesma cor, mas em diferentes matizes e cores.

Este próximo exemplo precisa (na data de publicação deste artigo) do Safari TP 120+. Compare esses 2 degradês, o superior mostrando várias cores HSL, todas com leveza de 50%, e o inferior, várias cores LCH, todas com leveza de 50%:

[Exemplo no CodePen](https://codepen.io/anon/pen/xxgOZQR)

Eis um screenshot com a diferença para aqueles que não têm acesso ao Safari TP 120+:

![Dark Mode: diferença entre cores HSL e LCH.](/images/blog/dark-mode-lch/image.webp)

Perceba que, em HSL, algumas cores (como amarelo e ciano) são muito mais claras do que outras. No LCH, todas as cores com a mesma luminosidade têm... A mesma luminosidade.

Tenha em mente que o croma de LCH não corresponde realmente à luminosidade de HSL, então, embora esteja definido com o mesmo número, ele não corresponde à mesma coisa.

Mas, nesse caso, como essa a técnica de **Dark Mode com variáveis de luminosidade invertidas funcionaria com cores LCH?** Vamos experimentar!

Foi usada [esta ferramenta](https://css.land/lch/) para converter as cores HSL existentes para LCH; depois, ajustados os valores um pouco manualmente, já que as cores inicialmente convertidas não pareciam boas em todas as luminosidades LCH -- observe que as cores HSL com as mesmas matiz e saturação podem ter matizes diferentes e chromas em LCH.

Esta é a aparência desta técnica com as cores LCH (no momento, Safari TP 120+ ou posterior para visualizar):

[Exemplo no CodePen](https://codepen.io/anon/pen/MWJKqBK)

Um screenshot, caso você não tenha conseguido ver direto no navegador:

<figure>

![Dark Mode: diferença entre modo claro e modo escuro usando variáveis de cor LCH.](/images/blog/dark-mode-lch/image2-1024x375.webp)

<figcaption>

Light mode e dark mode, gerados "automaticamente" com com variáveis de luminosidade invertidas LCH.

</figcaption>

</figure>

Não apenas o dark mode fica muito melhor, mas, mesmo no light mode, as 2 cores alternativas parecem mais uniformes, pois têm a mesma luminosidade LCH.

Aqui está uma comparação dos 2 dark modes:

<figure>

![Comparação dos 2 dark modes gerados automaticamente via claridade HSL (esquerda) e claridade LCH (direita).](/images/blog/dark-mode-lch/image3-1024x365.webp)

<figcaption>

Comparação dos 2 dark modes gerados "automaticamente" via claridade HSL (esquerda) e claridade LCH (direita).

</figcaption>

</figure>

Na verdade, até que as cores LCH sejam suportadas de forma confiável em todos os lugares, você precisa fornecer um substituto por meio de [@supports](/css/css-supports-deteccao-nativa-suporte-css/), mas, para ser breve, isso não foi incluído nesse demo.

## Automatizando a geração de variáveis de luminosidade

Se estiver usando um pré-processador que suporta loops, como [Sass](/css/o-basico-de-sass/), você pode automatizar a geração dessas variáveis e torná-las ainda mais granulares, por exemplo, com steps de 5%:

```scss
:root {
  @for $i from 0 through 20 {
    --l-#{$i * 5}: #{$i * 5}%;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    @for $i from 0 through 20 {
      --l-#{$i * 5}: #{100 - $i * 5}%;
    }
  }
}
```

## Dá para fazer variáveis de luminosidade sem tanta repetição?

Através dos exemplos, houve muita repetição de valores, sendo preciso declarar, por exemplo, `--l-40` como 40% e, em seguida, definindo-o para 60% no dark mode. Será que não é possível derivá-lo de alguma forma, subtraindo o valor que já se tem de 100%?

Aqueles com alguma experiência em programação podem arriscar algo como:

```scss
--l-40: calc(100% - var(--l-40));
```

Entretanto, isso não funcionará. CSS não é uma linguagem imperativa; não possui etapas de cálculo, em qu as variáveis possuem valores diferentes antes e depois de cada etapa. Não existe tal conceito de tempo; todas as declarações que são aplicadas atualmente, precisam ser verdadeiras ao mesmo tempo.

É mais semelhante à avaliação reativa (_reactive evaluation_) de fórmulas de planilhas do que à computação em JS e outras linguagens de programação populares (existem linguagens de programação reativas de propósito geral, mas são menos conhecidas).

Então, declarações como a acima são consideradas ciclos: uma vez que `--l-40` não pode se referir a si mesmo, isso é um erro, e `--l-40` seria definido com seu valor inicial como um mecanismo de recuperação de erro (já que o CSS não pode lançar erros).

Mas, então, existe ou não uma maneira de evitar declarar variáveis de claridade 2 vezes, uma para o modo claro e outra para o modo escuro?

_Existe_. Mas, talvez, não seja tão recomendada. **Isso torna o código mais complicado de ler e compreender, com poucos benefícios.** Mas por uma questão de diversão intelectual, vamos ver como é.

Em vez de definir `--l-40` para 40%, vamos defini-lo em termos de _sua diferença_ de 50%, (i.e., `-10%`). Então, `calc(50% + var(--l-40))` retorna 40% e `calc(50% - var(--l-40))` retorna 60%, os 2 valores de que precisamos. Podemos, portanto, declarar uma variável que é `-1` no dark mode e 1 no light mode e apenas multiplicar com isso.

Eis um subconjunto de como o código ficaria:

```scss
:root {
  --dm: 1;

  /* Declaração de exemplo: */
  --l-40: -10%;
}

@media (prefers-color-scheme: dark) {
  :root {
    --dm: -1;
  }
}

/* Examplo de uso: */

footer {
  color: hsl(0 0% calc(50% + var(--dm) * var(--l-40));

}
```

Ficou mais claro porque não é tão recomendado? Torna o uso muito mais complicado e dificílimo de ser lido. É esse tipo de adesão obsessiva à DRY que, com o tempo, os programadores acabamos percebendo que é contraproducente.

## Conclusão

Lembremo-nos que [Dark Mode é somente uma opção estética](/acessibilidade/dark-mode-opcao-estetica/), não um milagre da acessibilidade que muda vidas ou aumenta a produtividade, como muitos divulgam.

Independentemente disso, fato é que é preciso saber implementar modo escuro, especialmente nesses dias, em que grandes empresas e produtos estão aderindo à opção estética cada vez mais -- em outras palavras, o assunto está "hypado".

No momento, a técnica de fazer dark mode com variáveis de luminosidade invertidas LCH não tem amplo suporte dos navegadores, mas, brevemente, essa realidade mudará e, o quanto antes você souber técnicas variadas para implementar dark mode de maneira eficiente, melhor para você e para a Web.

---

Fonte: [dpw - desenvolvimento para web](https://dpw.dev/) (pt-BR).
Guia para agentes: https://dpw.dev/llms.txt
Índice completo: https://dpw.dev/sitemap-index.xml
Feed: https://dpw.dev/rss.xml
