Temas de cores com recursos CSS de referência

David A. Herron
David A. Herron

Publicado em: 11 de dezembro de 2025

Então, você tem um site que quer criar ou reformular. Talvez você tenha algumas cores principais em mente e esteja pensando em como implementar rapidamente um tema com base nelas.

Você vai precisar da cor principal, mas também de cores para ações, estados de passar o cursor, erros e outras necessidades da interface do usuário. E as opções de modo claro e escuro? De repente, você precisa de muitas cores, e isso pode ser confuso.

A boa notícia é que, quando se trata de criar uma paleta relativa aos tokens de cores que definem seu site e alternar entre modos de cor, os recursos do Baseline podem fazer muito do trabalho pesado para você. Você pode conferir algumas dessas técnicas na demonstração em destaque, uma playlist com tema de cores no site fictício da Baseline Radio.

Criar uma base com cores relativas

Se você tiver uma ideia de cor primária para seu tema, com alguma teoria básica de cores e a sintaxe de cores relativas do CSS, poderá começar a gerar rapidamente uma paleta de cores para usar no tema.

Digamos que sua cor base seja um tom de azul-petróleo, que você pode definir no formato de cor preferido. Em seguida, use qualquer função de cor para criar novas cores em relação à cor base:

html {
  --base-color: oklch(43.7% 0.075 224);
}

A propriedade personalizada --base-color é criada usando a função de cor oklch(). O OkLCh é a forma cilíndrica do espaço de cores Oklab e define valores para três canais: L (luminosidade), C (croma), H (matiz), além de um canal alfa opcional para controlar a transparência.

O OkLCh é um bom formato para esse tipo de manipulação de cores, já que foi projetado para oferecer uniformidade perceptual. Por exemplo, se você ajustar apenas o matiz de uma cor, a cor resultante terá um brilho e um croma semelhantes à cor original. Isso é especialmente útil para evitar problemas inesperados de contraste.

Mantendo a mesma luminosidade e croma do seu --base-color, ajuste a tonalidade em 120 graus nas duas direções para uma paleta triádica.

html {
  /* ... */
  --triadic-color-primary: oklch(from var(--base-color) l c calc(h + 120));
  --triadic-color-secondary: oklch(from var(--base-color) l c calc(h - 120));
}

Como mostrado aqui, a sintaxe de cor relativa usa uma função de cor que faz referência a uma cor de origem (--base-color neste exemplo) com a palavra-chave from e ajusta os respectivos canais do espaço de cores com base na cor de saída escolhida, que, neste caso, também será OkLCh.

A saída resultante oferece um rosa escuro para o --accent-color e um tom de dourado para o --highlight-color, ambos com o mesmo brilho e croma do --base-color original.

html {
  /* ... */
  --accent-color: var(--triadic-color-primary);
  --highlight-color: var(--triadic-color-secondary);
}

  html {
    /* Input color in the rgb color space*/
    --base-color: teal;

     /* Output color in oklch. Computes to oklch(0.543123 0.0927099 314.769) */
     --triadic-color-primary: oklch(from var(--base-color) l c calc(h + 120));
  }

Uma cor complementar adicionaria 180 graus ao ângulo de matiz.

html {
  /* ... */
  --complement-color: oklch(from var(--base-color) l c calc(h + 180));
  --border-highlight: var(--complement-color);
}

Para um estado de passar o cursor na sua interface, talvez você queira gerar uma versão mais clara de uma cor específica. Isso significa aumentar o valor do canal de luminosidade. Para um estado ativo, talvez você queira adicionar transparência ajustando o canal alfa ou escurecendo-o ao diminuir o valor do canal de luminosidade.

html {
  /* Darken the --base-color by 15% */
  --base-color-darkened: oklch(from var(--base-color) calc(l * 0.85) c h);
  /* Assign this color a meaningful variable name */
  --action-color: var(--base-color-darkened);
  /* Lighten the --action-color by 15% */
  --action-color-light: oklch(from var(--action-color) calc(l * 1.15) c h);
  /* Darken the --action-color by 10% */
  --action-color-dark: oklch(from var(--action-color) calc(l * 0.9) c h);
}

Aqui, estamos derivando o --action-color do --base-color e usando-o para botões e links. O --action-color tem duas variantes, mais clara e mais escura, que ainda seriam aplicadas mesmo que o --action-color fosse mudado para ser relativo a outra cor diferente do --base-color.

É possível ajustar os canais usando uma função matemática, como calc(), ou substituir o canal inteiro por um novo valor. Os canais inalterados são representados pelas respectivas letras (por exemplo, l para um valor de luminosidade inalterado).

Misturar cores com color-mix()

Para outras variantes de cor, você pode usar uma abordagem semelhante e ajustar outros canais da propriedade personalizada --base-color. Ou use color-mix() para adicionar dicas da cor base a outros aspectos do design.

O --border-color é uma mistura da cor de base e da cor nomeada grey, interpolada no espaço de cores oklab. Quando usado como um método de interpolação de cores, ele fornece resultados perceptualmente uniformes.

html {
  --base-mix-grey-50: color-mix(in oklab, var(--base-color), grey);
  --border-color: var(--base-mix-grey-50);
}

Por padrão, seria 50% de cada cor, mas é possível aumentar ou diminuir a proeminência de uma cor ajustando a porcentagem de peso dela.

html {
  --background-mix-base-80: color-mix(in oklab,
    var(--background-color) 80%,
    var(--base-color));
  --surface-light: var(--background-mix-base-80);
}

Uma alternativa para adicionar mais cor a um elemento é ajustar o canal de croma usando a sintaxe de cor relativa. A borda das entradas de texto no formulário de contato fica um pouco mais colorida quando está em foco.

[data-input*="text"] {
  --focus-ring: transparent;
  /* ... */
  &:focus {
    --focus-ring: oklch(from var(--border-color) l calc(c + 0.1) h);
  }
}

Ativar os modos claro e escuro

Depois de ter um conjunto de cores para trabalhar, você vai querer uma maneira eficiente de aplicar cores diferentes para os modos claro e escuro.

Indicar suporte para temas claros e escuros com a propriedade color-scheme

Você pode informar instantaneamente ao navegador que seu site pode ser visualizado nos modos "claro", "escuro" ou ambos com a propriedade color-scheme. Essa propriedade informa ao navegador em quais esquemas de cores um elemento pode ser renderizado confortavelmente.

 html {
   color-scheme: light dark;
}

Definir color-scheme: light dark no pseudoelemento :root ou no elemento html:

  • Informa ao navegador que sua página pode ser visualizada no modo claro ou escuro.
  • Muda as cores padrão da interface do usuário do navegador para corresponder à configuração do sistema operacional.

Para avisar os user agents com antecedência que sua página é compatível com os modos claro e escuro, você também pode sinalizar a compatibilidade com a troca de esquema de cores adicionando um elemento <meta> no <head> do documento.

<head>
  <!-- ... -->
   <meta name="color-scheme" content="light dark">
</head>

Definir variantes "claro" e "escuro" com a função light-dark()

Como autor, você pode estar acostumado a definir as cores de uma página com uma consulta prefers-color-scheme @media.

@media (prefers-color-scheme: light) {
  html {
    --background-color: oklch(95.5% 0 162);
    --text-color: black;
  }
}

@media (prefers-color-scheme: dark) {
  html {
    --background-color: oklch(22.635% 0.01351 291.83);
    --text-color: white;
  }
}

Isso funciona muito bem para cores e estilos controlados pelo autor, mas, como mencionado na seção anterior, você ainda precisaria de color-scheme para atualizar as cores da interface do navegador.

Mudar as cores da página com uma consulta prefers-color-scheme também significa duplicação de código, já que você precisa definir cores para cada modo separadamente.

Com color-scheme definido na página geral (ou em elementos específicos), é possível usar a função light-dark() para definir cores para cada modo em uma linha de código.

A função aceita duas cores. O primeiro é usado quando o esquema de cores está definido como "claro", e o segundo, quando está definido como "escuro".

html {
  color-scheme: light dark;
  /* Color custom property values for both light and dark modes */
  --base-color: light-dark(oklch(43.7% 0.075 224), oklch(89.2% 0.069 224));
  --background-color: light-dark(oklch(95.5% 0 162), oklch(22.635% 0.01351 291.83));
  --accent-color: oklch(from var(--base-color) l c calc(h + 120));
  --active-color: light-dark(var(--action-color-light), var(--action-color-dark));
  /* ... */
}

Assim como em qualquer propriedade personalizada, as configurações light-dark() das cores podem ser definidas globalmente ou em componentes específicos e usadas em outros lugares conforme necessário.

/* custom property usage */
body {
  background-color: var(--background-color);
  /* ... */
}

:any-link {
  /* ... */
  text-decoration-color: var(--accent-color);
}

Dar o controle aos usuários com o seletor de temas integrado

É ótimo ter um tema que se adapta às preferências de cores padrão do sistema ou do navegador de um usuário, mas você pode ir além e dar aos visitantes do seu site a capacidade de substituir essas preferências de cores padrão.

Se você criar uma alternância de tema que atualiza o atributo data-scheme no elemento <html>, poderá usar o mesmo atributo para mudar o color-scheme com CSS.

html {
  color-scheme: light dark;

  &[data-scheme="light"] {
    color-scheme: light;
  }

  &[data-scheme="dark"] {
    color-scheme: dark;
  }

  &[data-scheme="green"] {
      --base-color-light: oklch(48.052% 0.11875 151.945);
      --base-color-dark: oklch(92.124% 0.13356 151.558);
      color-scheme: light dark;
   }
}

data-scheme="light" e data-scheme="dark" mostram a página apenas nos respectivos modos de cor. O data-scheme="green" pode ser visualizado em qualquer modo e também muda o --base-color para um tom de verde, o que oferece uma paleta totalmente nova, já que a maioria das outras cores é baseada no --base-color.

Registrar propriedades personalizadas com @property

Até agora, as cores na demonstração foram definidas como propriedades personalizadas padrão. Também é possível registrar propriedades com a regra @property para aproveitar os benefícios da verificação de tipos.

Como o --base-color é usado como base de muitas outras cores na interface, é bom garantir que ele seja sempre uma cor e tenha um valor substituto.

@property --base-color-light {
  syntax: '<color>';
  inherits: false;
  initial-value: oklch(43.7% 0.075 224);
}

@property --base-color-dark {
  syntax: '<color>';
  inherits: false;
  initial-value: oklch(89.2% 0.069 224);
}

html {
  --base-color: light-dark(var(--base-color-light), var(--base-color-dark));
}

Assim, se o --base-color for mudado inadvertidamente para um valor inválido, ele sempre vai voltar para o initial-value definido com a regra @property.

Registrar determinadas propriedades dessa forma também permite animar cores de maneira suave em um linear-gradient().

.main-heading {
  background: linear-gradient(in oklch 90deg, var(--text-color) 50%, oklch(from var(--base-color) l c var(--header-hue)));
  background-clip: text;
  color: transparent;
  animation: header-hue-switch 5s ease-in-out infinite alternate;
}

.main-heading tem um plano de fundo linear-gradient() que é mostrado pelo texto transparente com a propriedade background-clip.

Uma parte do texto mostra um hue que, usando a sintaxe de cor relativa, anima de um valor de canal de 26.67 para 277:

@keyframes header-hue-switch {
  from {
    --header-hue: 26.67;
  }

  to {
    --header-hue: 277;
  }
}

Com uma propriedade personalizada --header-hue registrada, essa animação acontece sem problemas porque o navegador sabe que essa propriedade personalizada é um número.

@property --header-hue {
  syntax: '<number>';
  inherits: false;
  initial-value: 100;
}

Com uma propriedade personalizada não registrada, o navegador não saberia o tipo de dados de --header-hue. Portanto, a transição para um número seria uma animação discreta, que alternaria entre estados sem interpolação gradual.

Conclusão

As novas ferramentas de linha de base ajudam você a criar rapidamente uma paleta de cores ajustável e tornam a criação de variáveis de cor um processo mais eficiente. No entanto, você ainda vai precisar escolher entre as infinitas opções e combinações de cores.

Criar sua paleta dinamicamente assim oferece flexibilidade. Se você precisar mudar a cor base para branding, basta atualizar o --base-color, e o restante do tema vai seguir essa mudança. Ou, se você adicionar recursos de reprodução de música, poderá decidir mudar dinamicamente a cor de base para corresponder à música em reprodução.

Créditos

Lógica do seletor de tema adaptada do Componente de troca de tema de Adam Argyle.