Modo Escuro

Nesta página

Um site decide entre claro e escuro de uma de duas maneiras: a preferência do sistema do visitante, ou uma escolha explícita que o site armazena para esse visitante. Os componentes EmDash leem ambos os sinais através de uma convenção no elemento <html>. Esta página descreve essa convenção, como dar a um campo de imagem uma variante escura e como renderizá-la com o componente Image de emdash/ui.

Convenção de tema

Componentes e templates usam estes dois sinais, nesta ordem:

  1. Uma classe dark ou light no <html> fixa o esquema. A classe vence sobre a preferência do sistema.
  2. Sem classe, o esquema segue a media query prefers-color-scheme.

Os templates incluídos armazenam uma escolha explícita em um cookie theme e a aplicam antes do primeiro paint com um script inline no <head>. O script a seguir lê o cookie e define a classe, e não faz nada quando nenhuma escolha está armazenada:

<script is:inline>
	(function () {
		var c = document.cookie;
		var i = c.indexOf("theme=");
		var theme = i >= 0 ? c.slice(i + 6).split(";")[0] : null;
		if (theme === "dark" || theme === "light") {
			document.documentElement.classList.add(theme);
		}
	})();
</script>

Defina cores uma vez com light-dark() e deixe a classe fixar o esquema:

:root {
	color-scheme: light dark;
	--color-bg: light-dark(#ffffff, #0d0d0d);
	--color-text: light-dark(#1a1a1a, #ededed);
}
:root.light {
	color-scheme: light;
}
:root.dark {
	color-scheme: dark;
}

Um site sem alternador de tema não precisa de script: deixe <html> sem classe e a preferência do sistema se aplica.

Variantes escuras de imagem

Um campo de imagem pode carregar uma segunda imagem para esquemas de cores escuros. Editores a escolhem ao lado da imagem principal, e o componente Image mostra a que corresponde ao esquema do visitante.

Ativar o slot em um campo

O slot está desativado por padrão. Ative-o por campo, seja no admin ou em um arquivo seed.

No admin, abra Content Types, edite o campo de imagem e ative Dark mode variant.

Em um arquivo seed, defina a opção de widget darkVariant no campo:

{
	"slug": "featured_image",
	"label": "Featured Image",
	"type": "image",
	"options": { "darkVariant": true }
}

Escolher a variante no editor

  1. Abra uma entrada e selecione a imagem principal como de costume.

  2. Clique em Add dark mode variant abaixo da imagem e escolha a variante escura da biblioteca de mídia.

  3. Salve a entrada.

A variante é armazenada dentro do valor do campo como darkVariant. Remover a imagem principal remove a variante junto; substituir a imagem principal mantém a variante até você substituí-la ou removê-la.

Renderizar a variante

O componente Image renderiza ambas as imagens quando o valor carrega uma darkVariant e mostra a correspondente com CSS. Nada muda no template:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image } from "emdash/ui";

const slug = decodeSlug(Astro.params.slug);

if (!slug) {
  return Astro.redirect("/404");
}

const { entry: post } = await getEmDashEntry("posts", slug);

if (!post) {
  return Astro.redirect("/404");
}
---

{post.data.featured_image && <Image image={post.data.featured_image} priority />}

A saída contém dois elementos <img>. A imagem principal recebe a classe emdash-image--light e a variante recebe emdash-image--dark. Ambas usam o texto alternativo, as sobrecargas de largura e altura, e os atributos de carregamento da imagem principal. Cada uma mantém sua própria cor de placeholder.

Um id que você passa fica na imagem principal; a variante recebe o mesmo id com um sufixo --dark, então id="hero" produz hero e hero--dark.

Quando a imagem escura vem de outro lugar, como um segundo campo de imagem, passe-a explicitamente:

<Image image={post.data.hero} darkVariant={post.data.hero_dark} />

Comportamento de carregamento

Ambas as imagens são lazy por padrão. Navegadores não buscam uma imagem lazy que está oculta com display: none, então um visitante baixa apenas a variante para seu esquema, e a outra carrega quando o esquema muda.

Com priority, ambas as imagens recebem loading="eager" e fetchpriority="high", e ambas são baixadas em cada esquema. O tema é decidido no navegador, então o servidor não pode saber qual variante um visitante verá. Use priority na única imagem acima da dobra e deixe as outras imagens lazy.

Usar uma convenção de tema diferente

O CSS incluído oculta a variante que não corresponde ao esquema. Seus seletores usam :where() na parte do <html>, então qualquer regra sua que aponte para <html> com uma classe ou atributo vence.

Se seu alternador define um atributo como data-theme, a correção mais curta é também definir as classes dark e light do mesmo caminho de código. Caso contrário, sobrescreva os quatro casos em sua própria folha de estilos:

:root[data-theme="dark"] .emdash-image--light,
:root[data-theme="light"] .emdash-image--dark {
	display: none;
}
:root[data-theme="dark"] .emdash-image--dark,
:root[data-theme="light"] .emdash-image--light {
	display: block;
}

Combine o valor de display com o que sua folha de estilos dá às imagens em outros lugares, por exemplo inline quando você não redefine img para block.