Temas claros/escuros específicos de componentes

Recursos da Web: color-scheme light-dark()

Embora seja mais comum definir a propriedade color-scheme na raiz, ela pode ser definida em elementos individuais para forçar um esquema de cores diferente do restante da página. Isso pode ser útil para componentes que precisam ser sempre visualizados em um esquema de cores específico (por exemplo, sempre no modo escuro ou claro).

Como exemplos de casos de uso, temos:

  • Elementos que costumam estar no modo escuro, mesmo em páginas no modo claro, por motivos estéticos, como blocos de código, players de mídia e galerias de fotos
  • As áreas que contêm mídia projetada para um fundo claro (por exemplo, imagens, vídeos, ilustrações, visualizações de impressão) podem ser definidas para o modo claro, mesmo que o restante da página esteja no modo escuro.
  • Elementos cujo esquema de cores é controlado por uma configuração no nível do usuário, como prévias de componentes
  • Incorporações que não são compatíveis com os modos claro e escuro
  • Ferramentas de design, mapas, visualizações, jogos etc.

Quando mudar as cores e quando forçar o color-scheme?

Nem todo elemento que usa texto mais claro em um plano de fundo mais escuro no modo claro ou texto mais escuro em um plano de fundo mais claro no modo escuro precisa de um color-scheme diferente. Por exemplo, um botão principal pode ser renderizado em azul com texto branco no modo claro, mas isso não garante um color-scheme: dark.

Como regra geral, os elementos que usam um color-scheme diferente costumam ser superfícies complexas que estabelecem o próprio contexto visual, em vez de contêineres simples e rasos.

Ao considerar o uso de um color-scheme diferente em um elemento, pergunte-se:

  • A interface integrada do navegador que não é personalizada (por exemplo, controles de formulário, barras de rolagem etc.) deve usar esse esquema de cores ou se adaptar ao esquema de cores da página? -> Se for o primeiro caso, não use color-scheme.
  • As cores de light-dark() devem ser resolvidas como no restante da página ou com base na substituição? -> Se for o primeiro caso, não use color-scheme.
  • Os descendentes devem estar nesse color-scheme? Caso contrário, não use color-scheme.

Implementação básica

As substituições específicas de componentes geralmente são usadas (embora não necessariamente) em páginas que também oferecem suporte a vários esquemas de cores por meio de um color-scheme global. Para implementar o modo escuro em toda a página, consulte Modo escuro.

Depois que um color-scheme em toda a página estiver em vigor e você estiver usando tokens de cor sensíveis a ele (por exemplo, via light-dark()), basta definir color-scheme em componentes específicos para substituir o modo de cor dessa subárvore:

pre, code, .dark {
  color-scheme: dark;
}

Alguns navegadores adaptam automaticamente os componentes a um esquema de cores diferente. Para forçar o esquema de cores especificado em todos os casos, use only, ou seja, color-scheme: only dark; em vez de color-scheme: dark;.

Como adaptar valores que não são de cor

No momento, light-dark() só funciona para cores.

Práticas recomendadas

  • OBRIGATÓRIO: não defina color-scheme em elementos sem um plano de fundo, porque isso pode misturar pares de cores de plano de fundo e de texto de diferentes esquemas de cores, resultando em texto ilegível.
  • OPCIONAL: embora seja mais fácil reutilizar os mesmos pares de cores do modo escuro de toda a página, podemos definir pares de cores diferentes para esses componentes. Por exemplo, talvez você queira que um componente do modo escuro usado em uma página do modo claro seja um pouco menos escuro do que quando o mesmo componente é usado em uma página que está totalmente no modo escuro.

Problemas conhecidos que você precisa saber

Pegadinha importante: herança de cores light-dark()

light-dark() é resolvido no momento do valor computado. Isso significa que todas as propriedades <color> herdadas definidas como uma cor light-dark() vão transmitir apenas uma das duas cores para os descendentes, não a expressão light-dark() em si.

Isso inclui:

  • Propriedades de cor integradas que herdam, como color, accent-color, fill, stroke, text-shadow, caret-color
  • Todas as propriedades personalizadas herdáveis registradas com syntax: <color> e inherits: true
  • Qualquer outra propriedade <color> definida como inherit

Isso significa que você deve:

  • NÃO registre propriedades personalizadas destinadas a armazenar tokens de design (por exemplo, --surface-color) como <color>. Os tokens precisam manter a expressão light-dark() ativa para que os descendentes possam resolvê-los novamente em um color-scheme diferente.
  • Ao definir color-scheme em um elemento, especifique novamente as propriedades <color> herdadas que podem ter sido definidas como valores light-dark() (diretamente ou por tokens de design), mesmo que seja para o mesmo token de design.
  • NÃO use inherit em propriedades <color> em elementos com uma substituição color-scheme (pode ser usado nos descendentes).
  • RECOMENDAMOS usar propriedades <color> registradas para o caso de uso oposto: quando você quer capturar deliberadamente a cor resolvida do ancestral e impedir que ela seja resolvida novamente no color-scheme do descendente. Por exemplo, capturar o plano de fundo da página para usar em outro lugar.
  • Se você precisar animar uma cor, use uma propriedade <color> separada registrada em @property no elemento que está sendo animado (o registro é necessário para a interpolação de cores). Esse não é um token de design, mas um destino de animação por elemento, então não entra em conflito com a regra acima.

Exemplo:

:root {
    --accent-color: light-dark(blue, skyblue);
    --surface-color: light-dark(white, #222);
    --text-color: light-dark(#111, white);

    color-scheme: light dark;
    accent-color: var(--accent-color);
    color: var(--text-color);
}

body {
    /* --surface-color dynamically switches despite being inherited because --surface-color is not registered */
    background: var(--surface-color);
}

pre, code {
    color-scheme: dark;
    background: var(--surface-color);

    /* Without this, accent-color would be blue, not skyblue! */
    accent-color: var(--accent-color);

    /* Without this, text-color would be #111, not white! */
    color: var(--text-color);
}

Problemas que podem ocorrer ao usar color-scheme

  • O Chrome e o Firefox respeitam color-scheme para iframes: eles renderizam páginas incorporadas no esquema de cores correto e ajustam a consulta de mídia prefers-color-scheme da página incorporada para refletir o color-scheme do contexto de incorporação. O Safari não faz isso e resolve prefers-color-scheme para 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 usando postMessage() (que também permite reagir a mudanças no ambiente de execução). No iframe, defina uma classe em <html> (e/ou color-scheme em :root) com base nesse indicador, em vez de usar prefers-color-scheme.
    • Se você controla apenas a página incorporada: não há uma maneira confiável de detectar o color-scheme do 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 protocolo postMessage) e documente-o para os incorporadores.

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 :root ou html.
  • 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);
}