Modo Oscuro

En esta página

Un sitio decide entre claro y oscuro de una de dos maneras: la preferencia del sistema del visitante o una elección explícita que el sitio guarda para ese visitante. Los componentes de EmDash leen ambas señales mediante una única convención en el elemento <html>. Esta página describe esa convención, cómo dar a un campo de imagen una variante oscura y cómo renderizarla con el componente Image de emdash/ui.

Convención del tema

Los componentes y las plantillas usan estas dos señales, en este orden:

  1. Una clase dark o light en <html> fija el esquema. La clase prevalece sobre la preferencia del sistema.
  2. Sin clase, el esquema sigue la media query prefers-color-scheme.

Las plantillas incluidas guardan una elección explícita en una cookie theme y la aplican antes del primer renderizado con un script en línea en <head>. El siguiente script lee la cookie y establece la clase, y no hace nada cuando no hay ninguna elección guardada:

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

Define los colores una sola vez con light-dark() y deja que la clase fije el 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;
}

Un sitio sin selector de tema no necesita ningún script: deja <html> sin clase y se aplica la preferencia del sistema.

Variantes oscuras de imagen

Un campo de imagen puede llevar una segunda imagen para los esquemas de color oscuros. Los editores la eligen junto a la imagen principal, y el componente Image muestra la que coincida con el esquema del visitante.

Habilitar el slot en un campo

El slot está desactivado por defecto. Actívalo por campo, ya sea en el admin o en un archivo seed.

En el admin, abre Content Types, edita el campo de imagen y activa Dark mode variant.

En un archivo seed, establece la opción de widget darkVariant en el campo:

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

Elegir la variante en el editor

  1. Abre una entrada y selecciona la imagen principal como de costumbre.

  2. Haz clic en Add dark mode variant debajo de la imagen y elige la contraparte oscura de la biblioteca de medios.

  3. Guarda la entrada.

La variante se almacena dentro del valor del campo como darkVariant. Al quitar la imagen principal se quita también la variante; al reemplazar la imagen principal, la variante se conserva hasta que la reemplaces o la quites.

Renderizar la variante

El componente Image renderiza ambas imágenes cuando el valor lleva una darkVariant y muestra la correspondiente con CSS. La plantilla no cambia:

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

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

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

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

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

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

La salida contiene dos elementos <img>. La imagen principal recibe la clase emdash-image--light y la variante recibe emdash-image--dark. Ambas usan el texto alt de la imagen principal, las sobrescrituras de ancho y alto, y los atributos de carga. Cada una conserva su propio color de marcador de posición.

Un id que pases se queda en la imagen principal; la variante recibe el mismo id con el sufijo --dark, de modo que id="hero" produce hero y hero--dark.

Cuando la imagen oscura proviene de otro lugar, como un segundo campo de imagen, pásala explícitamente:

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

Comportamiento de carga

Ambas imágenes son lazy por defecto. Los navegadores no descargan una imagen lazy oculta con display: none, así que el visitante descarga solo la variante de su esquema, y la otra se carga cuando el esquema cambia.

Con priority, ambas imágenes reciben loading="eager" y fetchpriority="high", y las dos se descargan en cualquier esquema. El tema se decide en el navegador, por lo que el servidor no puede saber qué variante verá el visitante. Usa priority en la única imagen visible sin hacer scroll y deja las demás imágenes como lazy.

Usar una convención de tema diferente

El CSS incluido oculta la variante que no coincide con el esquema. Sus selectores usan :where() en la parte de <html>, así que cualquier regla tuya que apunte a <html> con una clase o un atributo prevalece.

Si tu selector establece un atributo como data-theme, la solución más corta es establecer también las clases dark y light desde la misma ruta de código. De lo contrario, sobrescribe los cuatro casos en tu propia hoja 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;
}

Haz coincidir el valor de display con el que tu hoja de estilos da a las imágenes en otros lugares, por ejemplo inline cuando no restableces img a block.