Como criar um esquema de cores

Uma visão geral básica de como estabelecer um esquema de cores dinâmico e configurável

Neste post, quero compartilhar ideias sobre como gerenciar vários esquemas de cores no CSS. Teste a demonstração.

Demo

Se preferir vídeos, confira a versão desta postagem no YouTube:

Visão geral

Vamos criar um sistema de cores acessível com propriedades personalizadas e calc() para criar uma página da Web que se adapte às preferências do usuário, mantendo a experiência de criação mínima. Começamos com uma cor de marca-base e criamos um sistema de variantes a partir dela: 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. Só no final é que eles são usados para mudar a página.

A marca

Muitas vezes, a cor da marca já foi estabelecida e é enviada como hexadecimal ou RGB. Este desafio de GUI tem uma cor de marca de base #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 20%, os três canais do valor de cor hsl precisam ser extraídos nas próprias propriedades personalizadas, como esta:

* {
  --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 matiz ajustando a saturação e a luminosidade do HSL.

Tema claro

Cada variante de cor será marcada com o esquema correspondente. Neste caso, cada é anexado com -light.

visualização dos resultados finais do tema claro

Marca

Começando pela cor da marca, ela é recriada 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 cores a seguir é 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, por ser muito escuro com 10% de luminosidade, mantém a saturação de 100% para que a cor da marca ainda apareça no azul-marinho escuro.

--text2-light, não é tão escura quanto a primeira cor, o que é bom, já que é uma cor secundária e também é muito menos saturada.

Cores da superfície

As cores da superfície são os planos de fundo, bordas e outras superfícies decorativas em que o texto fica ou em que está inserido. Em um tema claro, essas são as cores claras, em contraste com as cores de texto que eram escuras. Para criar cores claras com hsl, usaremos valores de porcentagem mais altos no terceiro valor de luminosidade. Também vamos diminuir a saturação para que os cinzas claros não fiquem muito saturados.

* {
  --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, para que o passar o cursor resulte em um aumento de contraste (99% de claridade para 92% de claridade, tornando-o mais escuro).

Sombras

As sombras em um esquema de cores são mais importantes, mas adicionam uma natureza realista ao efeito e ajudam a diferenciá-lo de sombras realistas com base preta. Para fazer isso, a cor da sombra vai usar a propriedade personalizada de matiz, ser levemente saturada com o matiz, mas ainda muito escura. Basicamente, criando uma sombra ligeiramente azul e muito escura.

* {
  --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. Pule para a seção "Sombra de raio" para saber mais.

Cores claras todas juntas

Não é preciso procurar para descobrir 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;
}
captura de tela das cores claras todas juntas
Sandbox no CodePen

Tema escuro

A maioria das marcas não começa com um tema escuro, é uma variante do tema principal, geralmente mais claro. Os usuários, por outro lado, geralmente escolhem um tema escuro para diferentes contextos, como à noite. Esses fatores me levaram a considerar dois aspectos com temas escuros:

  1. Os usuários geralmente estarão no escuro ao usar esse tema. Portanto, teste no escuro.
  2. As cores precisam ser desaturadas para não vibrar na tela por serem muito intensas.

visualização do resultado final do tema escuro

Marca

O tema claro usou os três valores dos canais de cor hsl da marca sem alterações, mas o tema escuro não. A saturação é cortada 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, aproximando-as 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 luminosidade e saturação baixas, com a 1ª superfície sendo 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. É aqui que --shadow-strength-dark é 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 dessa sombra. Você consegue notar a cor quando olha para a interface? Tente remover a saturação das ferramentas do desenvolvedor, qual você prefere?!

Cores escuras todas 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;
}
captura de tela das cores escuras todas juntas
Sandbox no CodePen

Tema escuro

Esse esquema de cores é sobre orquestrar a luminosidade e a saturação. Deve haver saturação suficiente para que a matiz ainda seja visível, mas também é necessário passar por pontuações de contraste, já que o objetivo é ter um contraste fraco e baixo.

Prévia dos resultados finais do tema escuro

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;
}

Escurecer 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;
}
captura de tela das cores escuras todas juntas
Sandbox no CodePen

Cores acessíveis

Observe como a menor luminosidade no conjunto de cores de texto escuro é de 65% e a maior luminosidade nas superfícies escuras é de 25%. Isso é 40% de espaço entre elas. No tema claro, há 55% de espaço para respirar no tema claro. Manter as diferenças de luminosidade entre o texto e as cores da superfície em cerca de 40 a 50% pode ajudar a manter as taxas de contraste de cores altas, além de ser uma variável sutil para ajustar caso as pontuações sejam baixas.

Eu chamo isso de "bump bump til ya pass", que é a interação de aumentar o valor de luminosidade até que uma ferramenta mostre que estou passando.

Pressione Shift + seta para baixo para diminuir a luminosidade e aumentar o contraste até passar

Cada um dos temas criados neste desafio passou nas pontuações de contraste. O esquema de cores escuras tem o menor contraste, mas ainda atende aos requisitos mínimos. Para ajudar outras pessoas da equipe a usar cores contrastantes, é uma boa ideia criar uma 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);
}
Captura de tela da superfície escura e dos pares de texto
Captura de tela da superfície escura e dos pares de texto com o VisBug

Sombra de Rad

Os temas usam uma classe utilitária chamada .rad-shadow. Essa sombra foi gerada na ferramenta Sombra suave, que eu adoro. 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 pudesse ser ajustada em cada esquema de cores.

cada sombra ao lado da outra

Para isso, criei duas variáveis para cada esquema de cores, uma cor de sombra 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 se trata de um esquema de cores escuras. O resultado final foi mais ou menos 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 com que os ângulos de sombra fossem um token de design constante, já que a direção da luz precisa ser a mesma entre todas as sombras do design.

Como usar os esquemas de cores

Com a pré-definição de cores concluída, é hora de transformá-las em propriedades independentes do esquema. Quero dizer que, como autor de CSS neste projeto de esquema de cores, raramente é necessário acessar o valor de um esquema de cores específico. Quero facilitar a manutenção do tema.

Para isso, o esquema de cores precisa ser usado exclusivamente pelas propriedades personalizadas genéricas, que vamos definir em breve. Dessa forma, as pessoas que usam as variáveis de design nunca precisam se preocupar com qual esquema de cores está definido no momento, elas só precisam usar as cores da superfície e do texto. Em vez de color: var(--text1-light), use color: var(--text1). Todas as adaptações e mudanças de cores são feitas em um nível muito mais alto no CSS.

Os estilos de conexão do tema claro no bloco de código a seguir 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 bem-sucedido! Vamos ter mais alguns desses momentos ao usar nossas cores predefinidas em outros contextos de esquemas 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);
}

Nesse ponto, os autores podem usar os esquemas de cores genéricos fornecidos conforme necessário e não precisam se preocupar com temas novamente.

Conclusão

Agora que você sabe como eu fiz, 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, envie um tweet para mim e vou adicionar à seção "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.