Uma visão geral fundamental de como estabelecer um esquema de cores dinâmico e configurável
Nesta postagem, quero compartilhar ideias sobre como gerenciar vários esquemas de cores em CSS. Teste a demonstração.
Se preferir vídeo, confira uma versão deste post no YouTube:
Visão geral
Vamos criar um sistema de cores acessível com propriedades personalizadas e calc() para
fazer uma página da Web adaptável às preferências do usuário, mantendo a experiência de
criação mínima. Começamos com uma cor base da marca e criamos um sistema de variantes com base nela: duas cores de texto, quatro cores de superfície e uma sombra correspondente.
Este guia começa definindo todas as cores de cada esquema de cores. Eles só são usados para mudar a página no final.
A marca
Muitas vezes, uma cor da marca já foi estabelecida e é entregue como hex ou rgb. Este desafio de GUI
tem uma cor de marca base de #0af. Primeiro, para esse sistema de cores, o valor hexadecimal precisa ser convertido em hsl.
* {
--brand: #0af;
--brand: hsl(200 100% 50%);
}
Para ativar um conceito de escurecimento ou clareamento da cor da marca, digamos, em 20%, os três canais do valor de cor hsl precisam ser extraídos para as próprias propriedades personalizadas, assim:
* {
--brand-hue: 200;
--brand-saturation: 100%;
--brand-lightness: 50%;
}
O CSS pode fazer cálculos nessas propriedades de cor, por exemplo, calc(var(--brand-lightness) -
20%) para diminuir o valor de luminosidade em 20%. Isso é fundamental para criar um
esquema de cores, já que o CSS pode manter todas as cores na mesma família de matizes ajustando os
níveis de saturação e luminosidade do hsl.
Tema claro
Cada variante de cor será marcada com o esquema correspondente. Neste caso, cada uma
é anexada com -light.

Marca
Começando pela cor da marca, ela é reconstruída envolvendo as propriedades personalizadas --brand-hue, --brand-saturation e --brand-lightness dentro dos parênteses da função hsl (), sem cálculos:
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
}
Cores do texto
Em seguida, os elementos essenciais de um esquema de cores precisam de cores de texto. Em um tema claro, o texto precisa ser muito escuro. Observe como a luminosidade das seguintes cores é baixa, bem abaixo de 50%.
* {
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
}
--text1-light, como está muito escuro com 10% de luminosidade, mantém a saturação pesada de 100% para que a cor da marca ainda possa aparecer no azul-marinho escuro.
--text2-light, não é tão escuro quanto a primeira cor, o que é bom, já que é uma cor secundária, e também é muito menos saturado.
Cores da superfície
As cores de superfície são os planos de fundo, bordas e outras superfícies decorativas em que o texto fica. Em um tema claro, essas são as cores claras, em oposição às cores do texto, que eram escuras. Para criar cores claras com hsl, vamos usar valores de porcentagem mais altos no terceiro valor de luminosidade. Também vamos diminuir a saturação para que os tons de cinza claros não pareçam muito coloridos.
* {
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
}
Quatro cores de superfície foram criadas, já que as cores decorativas tendem a precisar de mais variantes, para momentos interativos como :focus ou :hover ou para criar a aparência de camadas de papel. Nesses cenários, é bom fazer a transição de
--surface2-light ao passar o cursor para --surface3-light. Assim, o efeito resulta em um
aumento de contraste (99% de luminosidade para 92% de luminosidade, tornando-o mais escuro).
Sombras
As sombras em um esquema de cores são mais do que isso, mas adicionam uma natureza realista ao efeito e ajudam a destacar das sombras irreais baseadas em preto. Para isso, a cor da sombra vai usar a propriedade personalizada de matiz, ser levemente saturada com o matiz, mas ainda muito escura. Essencialmente, criando uma sombra muito escura e levemente azulada.
* {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
--surface-shadow-light não está encapsulado em uma função hsl. Isso ocorre porque o valor --shadow-strength será combinado para criar alguma opacidade, e o CSS precisa das partes para realizar cálculos. Acesse a seção de sombra
de radiação para saber mais.
Cores claras juntas
Não é necessário procurar como as cores claras são feitas, elas estão todas em um só lugar no CSS.
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
--surface-shadow-light: var(--brand-hue) 10% calc(var(--brand-lightness) / 5);
--shadow-strength-light: .02;
}
Tema escuro
A maioria das marcas não começa com um tema escuro, mas sim com uma variante do tema principal, geralmente mais claro. Já os usuários costumam escolher um tema escuro para contextos diferentes, como à noite. Esses fatores me levaram a manter duas coisas em mente com temas escuros:
- Os usuários geralmente ficam no escuro ao usar esse tema. Por isso, faça testes no escuro.
- As cores precisam ser dessaturadas para não vibrar na tela devido à intensidade excessiva.

Marca
O tema claro usou os três valores de canais de cores hsl da marca sem alteração, mas o tema escuro não. A saturação é reduzida pela metade, e a luminosidade é reduzida em 50%.
* {
--brand-dark: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 2)
calc(var(--brand-lightness) / 1.5)
);
}
Cores do texto
Em um tema escuro, as cores do texto precisam ser claras. As cores a seguir têm valores altos de luminosidade, o que as deixa mais próximas do branco.
* {
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
}
Cores da superfície
Em um tema escuro, as cores da superfície precisam ser escuras. As cores a seguir têm baixa luminosidade e saturação, e a primeira superfície é a mais escura, com 10%.
* {
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
}
Sombras
Em um tema escuro, as sombras podem ser muito difíceis de ver. Faz sentido, já que é difícil escurecer algo que já está bastante escuro. É aí que o
--shadow-strength-dark se torna muito útil, porque permite escurecer as
sombras mudando uma variável.
* {
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
Além disso, observe a saturação na sombra. Você consegue notar a cor ao olhar para a interface? Tente remover a saturação das ferramentas de desenvolvimento. Qual você prefere?
Cores escuras juntas
* {
--brand-dark: hsl(var(--brand-hue) calc(var(--brand-saturation) / 2) calc(var(--brand-lightness) / 1.5));
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
Tema escuro
Esse esquema de cores é sobre orquestrar luminosidade e saturação. A saturação precisa ser suficiente para que uma tonalidade fique visível, mas também precisa passar nos níveis de contraste, já que a intenção é que ela seja fraca e de baixo contraste.

Marca
* {
--brand-dim: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 1.25)
calc(var(--brand-lightness) / 1.25)
);
}
Cores do texto
* {
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
}
Cores da superfície
* {
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
}
Sombras
* {
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
Diminuir todas as cores
* {
--brand-dim: hsl(var(--brand-hue) calc(var(--brand-saturation) / 1.25) calc(var(--brand-lightness) / 1.25));
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
Cores acessíveis
Observe como a menor luminosidade no conjunto de cores de texto escuras é de 65% e a maior luminosidade nas superfícies escuras é de 25%. Isso é 40% de espaço de respiração de luminosidade entre eles. No tema claro, há 55% de espaço livre. Manter as diferenças de luminosidade entre o texto e as cores da superfície em torno de 40 a 50% pode ajudar a manter altas taxas de contraste de cores, além de ser uma alavanca sutil para ajustar caso as pontuações sejam baixas.
Eu chamo de "bump bump til ya pass", que é a interação de aumentar o valor de luminosidade até que uma ferramenta mostre que estou passando.
Todos os temas criados neste desafio têm pontuações de contraste aprovadas. O esquema de cores escuras tem o menor contraste entre eles, mas ainda atende aos requisitos mínimos. Para ajudar outras pessoas da equipe a usar cores contrastantes, crie um nome de classe que combine uma cor de superfície com uma cor de texto acessível.
.surface1 {
background-color: var(--surface1);
color: var(--text2);
}
.surface2 {
background-color: var(--surface2);
color: var(--text2);
}
.surface3 {
background-color: var(--surface3);
color: var(--text1);
}
.surface4 {
background-color: var(--surface4);
color: var(--text1);
}
Rad Shadow
Os temas usam uma classe utilitária chamada .rad-shadow. Essa sombra foi gerada com a ferramenta Sombra suave, que eu gosto muito. Peguei o snippet gerado e o personalizei com minhas próprias cores e cálculos de opacidade. O motivo disso foi criar uma sombra que eu pudesse ajustar em cada esquema de cores.

Para isso, criei duas variáveis para cada esquema de cores a ser ajustado: uma cor e uma intensidade de sombra. A cor é para ajustes de saturação e escuridão, enquanto a intensidade é uma maneira fácil de aumentar a intensidade da sombra quando é um esquema de cores escuras. O resultado final foi algo assim.
:root {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
.rad-shadow {
box-shadow:
0 2.8px 2.2px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 6.7px 5.3px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .01)),
0 12.5px 10px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 22.3px 17.9px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 41.8px 33.4px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 100px 80px hsl(var(--surface-shadow) / var(--shadow-strength))
;
}
Se eu fosse mais longe com as sombras no meu esquema de cores, também faria dos ângulos de sombra uma constante de token de design, já que a direção da luz deve ser a mesma entre todas as sombras do design.
Uso dos esquemas de cores
Com a pré-definição de cores concluída, é hora de transformá-las em propriedades agnósticas de esquema. O que quero dizer é que, como um autor de CSS dentro desse projeto de esquema de cores, raramente é necessário acessar o valor de um esquema de cores específico. Quero facilitar a permanência no tema.
Para isso, o uso do esquema de cores precisa ser feito exclusivamente pelas
propriedades personalizadas genéricas, que vamos definir em breve. Dessa forma,
quem usa as variáveis de design nunca precisa se preocupar com qual esquema de cores está
definido no momento. Basta usar as cores de superfície e de texto. Em vez de
color: var(--text1-light), use color: var(--text1). Toda a adaptação e a mudança de cores são feitas em um nível muito mais alto no CSS.
No bloco de código a seguir, os estilos conectivos do tema claro
conectam uma propriedade personalizada genérica com a cor específica do tema claro. Agora, todos os usos de var(--brand) vão usar a cor clara da marca.
Tema claro (automático)
:root {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
O site agora está usando o tema claro. Este é um momento muito divertido e de sucesso! Vamos ter mais alguns desses momentos ao usar nossas cores predefinidas em outros contextos de esquema de cores.
Tema escuro (automático)
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
}
Tema claro
[color-scheme="light"] {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
Tema escuro
[color-scheme="dark"] {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
Tema escuro
[color-scheme="dim"] {
color-scheme: dark;
--brand: var(--brand-dim);
--text1: var(--text1-dim);
--text2: var(--text2-dim);
--surface1: var(--surface1-dim);
--surface2: var(--surface2-dim);
--surface3: var(--surface3-dim);
--surface4: var(--surface4-dim);
--surface-shadow: var(--surface-shadow-dim);
--shadow-strength: var(--shadow-strength-dim);
}
Neste ponto, os autores podem usar os tipos genéricos de esquema de cores fornecidos conforme necessário e nunca mais precisam se preocupar com temas.
Conclusão
Agora que você sabe como eu fiz isso, como você faria? 🙂
Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Codepen ou hospede sua própria demonstração, me envie um tweet com ela e eu vou adicionar à seção de remixes da comunidade abaixo.
Origem
Remixes da comunidade
- @chris-kruining adicionou um controle deslizante de matiz,
cores de status e modos de contraste para no-preference, more e less:
demonstração.