A propriedade color-scheme indica quais esquemas de cores (como claro ou escuro) sua página aceita. Isso informa ao navegador que ele pode aplicar automaticamente temas aos elementos nativos da interface, como barras de rolagem, controles de formulário e o plano de fundo padrão da tela, para que correspondam ao design do site e ajudem a minimizar os flashes brancos durante o carregamento inicial.
Implementação
1. Declarar esquemas compatíveis em HTML
OBRIGATÓRIO: para evitar um "flash de conteúdo sem tema" (FOUC, na sigla em inglês), coloque uma tag <meta> no seu <head> para garantir que o navegador saiba quais temas são compatíveis antes mesmo de começar a renderização. Embora essa tag <meta> ajude a evitar o FOUC definindo a cor inicial da tela com antecedência, ela pode não eliminar completamente os flashes em todos os navegadores ou condições de carregamento.
<!-- MANDATORY: Declare support for both light and dark themes -->
<meta name="color-scheme" content="light dark">
2. Aplicar o esquema de cores de toda a página ao CSS :root ou html
OBRIGATÓRIO: aplique a propriedade color-scheme ao elemento html ou à pseudoclasse :root. Os navegadores procuram especificamente o elemento raiz para determinar o tema de toda a janela de visualização, incluindo as barras de rolagem raiz e o plano de fundo inicial da "tela". Se aplicadas apenas ao body, essas superfícies globais da interface podem permanecer no modo claro, porque o body não controla o contexto de renderização da janela.
/* MANDATORY: Apply color-scheme to :root or html for viewport-wide theming */
:root {
/* MANDATORY: Automatically adapt native UI to user system preferences */
color-scheme: light dark;
}
3. Definir tokens de cores claras e escuras
Use a função light-dark() para definir tokens de cor que se adaptam automaticamente a diferentes valores de color-scheme.
Também é recomendável manter os valores de cor brutos em propriedades personalizadas separadas, o que facilita a combinação de diferentes maneiras (e facilita o comportamento de substituição, se necessário).
Para ter mais controle sobre as cores da interface integrada, como accent-color ou scrollbar-color, os autores podem adicionar as próprias cores dinâmicas usando propriedades personalizadas e/ou a função light-dark(). Essa função escolhe automaticamente a cor correta com base no color-scheme calculado do elemento e elimina a necessidade de consultas de mídia redundantes, mas não é necessária para uma implementação básica.
:root {
--color-brand-light: oklch(45% 0.23 270);
--color-brand-dark: oklch(85% 0.15 210);
--color-brand-text-light: white;
--color-brand-text-dark: oklch(40% 0.23 270);
--color-brand: light-dark(var(--color-brand-light), var(--color-brand-dark));
--color-brand-text: light-dark(var(--color-brand-text-light), var(--color-brand-text-dark));
/* MANDATORY: Automatically adapt native UI to user system preferences */
color-scheme: light dark;
}
button.primary {
/* These automatically adapt to color scheme */
background-color: var(--color-brand);
color: var(--color-brand-text);
}
OPCIONAL: várias cores do sistema estão disponíveis e também se adaptam automaticamente ao esquema de cores usado (e a outros modos de cor, como cores forçadas), como canvas, canvastext, accentcolor (verifique a compatibilidade), buttonborder etc. Elas costumam ser muito limitadas para serem úteis, exceto em casos muito específicos em que você precisa corresponder exatamente a uma determinada interface padrão do navegador ou como substitutos/padrões.
OPCIONAL: adaptar pares de cores ao contexto
Mesmo ao substituir o padrão do sistema, pode ser útil usar a consulta de mídia prefers-color-scheme para definir pares de cores diferentes que considerem as cores do navegador e do Chrome do SO ao redor da página (ou da página ao redor, quando a página é usada como um iframe).
Por exemplo, use um tema claro um pouco mais escuro quando a configuração do sistema for dark ou um tema escuro mais contrastante quando a configuração do sistema for light, para que a página não seja visualmente dominada pela interface ao redor.
Personalização detalhada da interface do navegador
Definir color-scheme já adapta a interface do navegador ao esquema de cores usado, mas isso vai usar os padrões do SO e/ou as cores do sistema que podem não se alinhar perfeitamente ao design do site.
Os navegadores mais recentes expõem vários hooks de personalização detalhados para eles.
Não reimplemente controles nativos apenas para personalizar a aparência deles sem esgotar os ganchos de personalização que os navegadores mais recentes oferecem.
Como definir a cor de destaque
Algumas interfaces do navegador (por exemplo, caixas de seleção marcadas ou controles deslizantes) usam uma cor de destaque.
Por padrão, ela é resolvida para a configuração do SO, mas você pode usar a propriedade accent-color para definir uma cor que se alinhe melhor à página, como a cor da marca.
html {
accent-color: light-dark(var(--color-accent-light), var(--color-accent-dark));
}
Problemas que podem ocorrer ao usar accent-color
A renderização de accent-color no navegador varia.
Para indicadores geralmente pintados sobre um fundo de cor de destaque, como marcas de caixas de seleção e pontos de botões de opção, os navegadores podem aplicar tratamentos diferentes:
- O Chromium e o Firefox geralmente selecionam uma cor de indicador contrastante.
- O Safari pode preservar um indicador branco no modo claro ou um indicador preto no modo escuro, modificando a cor de destaque atrás dele. Esse ajuste nem sempre fornece contraste suficiente para todas as cores fornecidas. Por isso, teste combinações importantes no Safari, incluindo esquemas de cores claras e escuras. Não presuma que o Safari vai escolher automaticamente a cor do indicador que oferece o melhor contraste.
Cores da barra de rolagem
Você pode usar scrollbar-color com light-dark() para definir cores personalizadas da barra de rolagem que se adaptam ao esquema de cores usado.
:root {
--color-scrollbar-track: light-dark(#eee, #222);
--color-scrollbar-thumb: light-dark(#999, #666);
scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track);
}
Problemas que podem ocorrer ao usar scrollbar-color
- NÃO anime nem faça transições de
scrollbar-color. Um bug do WebKit (link em inglês) faz com que a barra de rolagem pisque sempre quescrollbar-colormuda. - No macOS, as propriedades
scrollbar-color(padrão) e::-webkit-scrollbar(legado) são ignoradas por padrão porque o macOS usa barras de rolagem de "sobreposição" nativas. Você PRECISA combinar cores personalizadas comscrollbar-width(por exemplo,thinouauto) para forçar o macOS a renderizá-las. - Mesmo com
scrollbar-widthaplicado, as barras de rolagem de sobreposição do macOS renderizam a faixa (calha) como transparente por padrão. Se o design exigir uma cor de fundo visível para a barra de rolagem no MacOS, você PRECISA aplicarscrollbar-gutter: stable;ao contêiner rolável, mas observe que ela só aparece depois que o usuário passa o cursor sobre a barra de rolagem. - Mesmo com
scrollbar-gutter: stable, a faixa pode ser transparente no MacOS. O polegar não deve depender da cor da faixa para ficar visível.
Mais personalização
A maioria das interfaces do navegador expõe pseudoelementos para personalizar totalmente a aparência, como:
::placeholder::spelling-error::grammar-error::selection::search-text::target-text::file-selector-button
Você pode usar as cores light-dark() em qualquer um deles para aplicar cores que se adaptam ao esquema de cores usado.
4. Imagens diferentes para os modos claro e escuro
Para imagens CSS, light-dark() também pode ser usado para fornecer valores de imagem diferentes.
IMPORTANTE: os dois valores em light-dark() precisam ser do mesmo tipo. Não é possível combinar uma cor com uma imagem.
Se um dos dois precisar ser uma imagem, use image(<color>) para unir o valor de cor.
Detecção de tema baseada em JS
A maior parte da ramificação do esquema de cores precisa ser feita em CSS, que se adapta automaticamente às mudanças.
Qualquer JS que leia matchMedia("(prefers-color-scheme: dark)").matches TAMBÉM PRECISA processar o evento change, já que a preferência do sistema pode mudar a qualquer momento.
OPCIONAL: implementar uma alternância de esquema de cores
Se você quiser permitir que os usuários substituam manualmente o padrão do sistema para este site, implemente um botão de alternância do modo escuro. A maioria dos sites não precisa de uma opção persistente na interface. Geralmente, basta usar o esquema de cores do sistema como padrão, com uma configuração opcional em uma superfície de configurações separada. Consulte Alternâncias do modo escuro para conferir as práticas recomendadas sobre como implementar uma alternância do modo escuro (persistente ou não).
OPCIONAL: substituições específicas de componentes
É possível substituir o tema global para elementos específicos definindo color-scheme neles.
Para mais informações e dicas, consulte Temas claros/escuros específicos de componentes.
Problemas conhecidos que você precisa saber
Problemas que podem ocorrer ao usar color-scheme
- O Chrome e o Firefox respeitam
color-schemepara iframes: eles renderizam páginas incorporadas no esquema de cores correto e ajustam a consulta de mídiaprefers-color-schemeda página incorporada para refletir ocolor-schemedo contexto de incorporação. O Safari não faz isso e resolveprefers-color-schemepara a configuração do sistema, mesmo dentro de iframes.- Se você controla o elemento pai e o iframe: transmita o esquema de cores do elemento pai para o iframe de forma explícita, usando um parâmetro de URL (
?theme=dark) no momento da criação do iframe ou usandopostMessage()(que também permite reagir a mudanças no ambiente de execução). No iframe, defina uma classe em<html>(e/oucolor-schemeem:root) com base nesse indicador, em vez de usarprefers-color-scheme. - Se você controla apenas a página incorporada: não há uma maneira confiável de detectar o
color-schemedo contexto de incorporação de dentro do iframe no Safari. Exponha um parâmetro de tema explícito na API de incorporação (por exemplo, uma string de consulta ou um protocolopostMessage) e documente-o para os incorporadores.
- Se você controla o elemento pai e o iframe: transmita o esquema de cores do elemento pai para o iframe de forma explícita, usando um parâmetro de URL (
Estratégias de fallback
Alternativas e suporte do navegador para color-scheme
A propriedade color-scheme é um aprimoramento progressivo.
Os navegadores que não oferecem suporte a essa propriedade vão ignorá-la e usar a interface padrão do modo claro.
Para se adaptar às preferências do usuário em navegadores mais antigos, use consultas de mídia prefers-color-scheme para fornecer cores diferentes quando o modo escuro for preferido.
- USE a consulta de mídia para alternar as propriedades personalizadas em
:rootouhtml. - Evite usar a consulta de mídia em componentes individuais, a menos que o componente exija um tipo muito específico de personalização do modo escuro além das cores.
:root {
/* Define brand colors for each mode */
--color-brand-light: #0056b3;
--color-brand-dark: #00e5ff;
--color-brand: var(--color-brand-light);
/* MANDATORY: Fallback for browsers without light-dark support */
@media (prefers-color-scheme: dark) {
--color-brand: var(--color-brand-dark);
}
/* Ignored in older browsers */
color-scheme: light dark;
}
button.primary {
background-color: var(--color-brand);
}
Alternativas e suporte do navegador para light-dark()
Para navegadores que oferecem suporte a color-scheme, mas ainda não a light-dark(), as versões claras e escuras das cores precisam ser definidas primeiro como propriedades personalizadas, e a consulta de mídia prefers-color-scheme precisa ser usada para definir cores para o respectivo modo, como no exemplo abaixo:
:root {
/* Define browser UI accent color for each mode */
--brand-accent-light: #0056b3;
--brand-accent-dark: #00e5ff;
--accent-color: var(--brand-accent-light);
/* MANDATORY: Fallback for browsers without light-dark support */
@media (prefers-color-scheme: dark) {
--accent-color: var(--brand-accent-dark);
}
/* OPTIONAL: use light-dark() for more control of built-in UI colors */
@supports (color: light-dark(white, black)) {
--accent-color: light-dark(var(--brand-accent-light), var(--brand-accent-dark));
}
/* MANDATORY: Automatically adapt native UI to user system preferences */
color-scheme: light dark;
/* Example inherited color property */
accent-color: var(--accent-color);
}
pre, code {
color-scheme: dark;
/* **Mandatory**: any inherited color properties must be set again, even if to the same design tokens */
accent-color: var(--accent-color);
}
Alternativas e suporte do navegador para valores de imagem light-dark()
Para navegadores que não são compatíveis com light-dark() para imagens, use image-set() como uma chave:
atribua a cada opção um type() de uma propriedade personalizada e defina a inativa como um tipo MIME não compatível para que o navegador descarte essa opção.
A opção ativa recebe um valor vazio, então é uma opção image-set() simples sem type().
Defina a chave uma vez em :root. Cada site de uso só precisa das duas imagens inline, como light-dark().
:root {
--light-type: ;
--dark-type: type("image/do-not-use");
/* MANDATORY: Fallback for browsers without light-dark() image support */
@media (prefers-color-scheme: dark) {
--light-type: type("image/do-not-use");
--dark-type: ;
}
}
.hero {
background-image: image-set(url(hero-light.png) var(--light-type), url(hero-dark.png) var(--dark-type));
@supports (background-image: light-dark(url("a"), url("b"))) {
background-image: light-dark(url(hero-light.png), url(hero-dark.png));
}
}
- Somente a imagem selecionada é baixada.
- As resoluções ainda funcionam:
image-set(url(a-1x.png) 1x var(--light-type), url(a-2x.png) 2x var(--light-type), url(b-1x.png) 1x var(--dark-type), url(b-2x.png) 2x var(--dark-type)). - NÃO use
type("")para a opção ativa. Os navegadores tratam a string vazia como incompatível e descartam todas as opções. - Ao contrário de
light-dark(), a chave não segue ocolor-schemedefinido em uma subárvore. Uma alternância manual ou qualquer subárvore que definacolor-schemeTAMBÉM PRECISA inverter--light-typee--dark-type.
Alternativa: uma propriedade personalizada por imagem
É mais simples quando apenas algumas imagens variam, mas cada imagem precisa de uma ramificação de consulta de mídia própria, e uma alternância manual precisa mudar cada uma delas.
:root {
--hero-image: url(hero-light.png);
@media (prefers-color-scheme: dark) {
--hero-image: url(hero-dark.png);
}
@supports (background-image: light-dark(url("a"), url("b"))) {
--hero-image: light-dark(url(hero-light.png), url(hero-dark.png));
}
}
.hero {
background-image: var(--hero-image);
}
Alternativas e suporte do navegador para image()
Use um gradiente com uma única parada de cor e sem posições, por exemplo, linear-gradient(blue).
Para um suporte mais amplo do navegador, você pode fornecer explicitamente posições 0 100% para a parada de cor única, por exemplo, linear-gradient(blue 0 100%).
Alternativas e suporte do navegador para scrollbar-color
Esse recurso é um aprimoramento progressivo e nem sempre requer substitutos.
Se o estilo for importante e o destino do usuário for "Baseline Widely Available" ou anterior, inclua os pseudoelementos ::-webkit-scrollbar não padrão como substitutos.
Envolva os substitutos legados em um bloco @supports not (scrollbar-color: auto) para evitar conflitos entre propriedades padrão e seletores WebKit legados em navegadores que oferecem suporte nativo a ambos.
Se você estiver usando propriedades personalizadas para definir cores, elas serão aplicadas automaticamente aos seletores legados do WebKit. NÃO é necessário duplicá-los.
/* Legacy fallback for WebKit/Blink browsers */
@supports not (scrollbar-color: auto) {
.scroller::-webkit-scrollbar {
/* Must define base size in WebKit for custom colors to be visual */
width: 12px;
height: 12px;
}
.scroller::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
}
.scroller::-webkit-scrollbar-track {
background: var(--scrollbar-track);
}
}
Alternativas e suporte do navegador para accent-color
A propriedade accent-color é um aprimoramento progressivo.
Os navegadores que não são compatíveis com essa propriedade vão ignorá-la e usar as cores padrão da interface.