Modo oscuro

La propiedad color-scheme indica qué esquemas de colores (como claro u oscuro) admite tu página. Esto informa al navegador que puede aplicar automáticamente un tema a los elementos nativos de la IU, como las barras de desplazamiento, los controles de formulario y el fondo predeterminado del lienzo, para que coincidan con el diseño de tu sitio y ayudar a minimizar los destellos blancos durante la carga inicial.

Implementación

1. Declara los esquemas admitidos en HTML

OBLIGATORIO: Para evitar un "destello de contenido sin tema" (FOUC), coloca una etiqueta <meta> en tu <head> para asegurarte de que el navegador sepa qué temas admites antes de que comience a renderizar. Si bien esta etiqueta <meta> ayuda a evitar el FOUC estableciendo el color inicial del lienzo de forma anticipada, es posible que no elimine por completo los destellos en todos los navegadores o condiciones de carga.

<!-- MANDATORY: Declare support for both light and dark themes -->
<meta name="color-scheme" content="light dark">

2. Aplica el esquema de colores de toda la página a :root o html de CSS

OBLIGATORIO: Aplica la propiedad color-scheme al elemento html o a la pseudoclase :root. Los navegadores buscan específicamente el elemento raíz para determinar el tema de todo el viewport, incluidas las barras de desplazamiento raíz y el fondo inicial del "lienzo". Si se aplica solo a body, estas plataformas de IU globales pueden permanecer en modo claro porque body no controla el contexto de renderización de la ventana.

/* 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. Define tokens de color claro y oscuro

Puedes usar la función light-dark() para definir tokens de color que se adapten automáticamente a diferentes valores de color-scheme.

También se recomienda que mantengas los valores de color sin procesar en propiedades personalizadas independientes, lo que facilita combinarlos de diferentes maneras (y facilita el comportamiento de resguardo, si es necesario).

Para tener más control sobre los colores de la IU integrada, como accent-color o scrollbar-color, los autores pueden, de forma opcional, agregar sus propios colores dinámicos con el uso de propiedades personalizadas o la función light-dark(). Esta función elige automáticamente el color correcto según el color-scheme calculado del elemento y elimina la necesidad de realizar consultas de medios redundantes, pero no es necesaria para una implementación 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: Hay varios colores del sistema disponibles, que también se adaptan automáticamente al esquema de colores utilizado (y a otros modos de color, p.ej., colores forzados), como canvas, canvastext, accentcolor (verifica la compatibilidad), buttonborder, etc. Por lo general, son demasiado limitados para ser útiles, más allá de casos muy específicos en los que necesitas que coincidan exactamente con cierta IU predeterminada del navegador o como resguardos o valores predeterminados.

OPCIONAL: Adapta los pares de colores al contexto

Incluso cuando se anula el valor predeterminado del sistema, puede ser útil usar la consulta de medios prefers-color-scheme para definir pares de colores diferentes que tengan en cuenta los colores del navegador y del Chrome del SO alrededor de la página (o de la página circundante, cuando la página se usa como un iframe).

Por ejemplo, usa un tema claro un poco más tenue cuando el parámetro de configuración del sistema sea dark o un tema oscuro con más contraste cuando el parámetro de configuración del sistema sea light, de modo que la página no se vea opacada por la IU circundante.

Personalización detallada de la IU del navegador

La configuración de color-scheme ya adapta la IU del navegador al esquema de colores utilizado, pero esto usará los valores predeterminados del SO o los colores del sistema que podrían no alinearse perfectamente con el diseño del sitio web. Los navegadores modernos exponen varios hooks de personalización detallados para estos. No vuelvas a implementar controles nativos solo para personalizar su apariencia sin agotar los hooks de personalización que proporcionan los navegadores modernos.

Cómo configurar el color de los elementos destacados

Algunos elementos de la IU del navegador (p.ej., casillas de verificación marcadas o controles deslizantes) usan un color de los elementos destacados. De forma predeterminada, se resuelve según la configuración del SO, pero puedes usar la propiedad accent-color para establecer un color que se alinee mejor con la página, como el color de la marca de la página.

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

Problemas que debes tener en cuenta cuando usas accent-color

La renderización de accent-color en el navegador varía. En el caso de los indicadores que, por lo general, se pintan sobre un fondo de color de los elementos destacados, como las marcas de las casillas de verificación y los puntos de los botones de selección, los navegadores pueden aplicar diferentes tratamientos:

  • Chromium y Firefox suelen seleccionar un color de indicador que contraste.
  • En cambio, Safari puede conservar un indicador blanco en el modo claro o un indicador negro en el modo oscuro, mientras modifica el color de los elementos destacados detrás de él. Este ajuste no siempre proporciona suficiente contraste para cada color proporcionado, por lo que debes probar las combinaciones importantes en Safari, incluidos los esquemas de colores claros y oscuros. No asumas que Safari elegirá automáticamente el color del indicador que proporcione el mejor contraste.

Colores de la barra de desplazamiento

Puedes usar scrollbar-color junto con light-dark() para establecer colores personalizados de la barra de desplazamiento que se adapten al esquema de colores utilizado.

: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 debes tener en cuenta cuando usas scrollbar-color

  • NO animes ni hagas transiciones de scrollbar-color. Un error de WebKit hace que la barra de desplazamiento parpadee cada vez que cambia scrollbar-color.
  • En macOS, las propiedades scrollbar-color (estándar) y ::-webkit-scrollbar (heredada) se ignoran de forma predeterminada porque macOS usa barras de desplazamiento nativas de "superposición". DEBES vincular los colores personalizados con scrollbar-width (p.ej., thin o auto) para forzar a macOS a renderizarlos.
  • Incluso con scrollbar-width aplicado, las barras de desplazamiento superpuestas de macOS renderizan la pista (canaleta) como transparente de forma predeterminada. Si el diseño requiere un color de fondo de pista visible en macOS, DEBES aplicar scrollbar-gutter: stable; al contenedor desplazable, pero ten en cuenta que solo aparece después de que el usuario coloca el cursor sobre la barra de desplazamiento.
  • Incluso con scrollbar-gutter: stable, es posible que la pista sea transparente en macOS. El control deslizante no debe depender del color de la pista para ser visible.

Más personalización

La mayoría de las IU de los navegadores exponen seudoelementos para personalizar por completo su apariencia, como los siguientes:

  • ::placeholder
  • ::spelling-error
  • ::grammar-error
  • ::selection
  • ::search-text
  • ::target-text
  • ::file-selector-button

Puedes usar colores light-dark() en cualquiera de estos para aplicar colores que se adapten al esquema de colores que se use.

4. Imágenes variables para los modos claro y oscuro

En el caso de las imágenes de CSS, también se puede usar light-dark() para proporcionar diferentes valores de imagen.

IMPORTANTE: Ambos valores en light-dark() deben ser del mismo tipo. No puedes combinar un color con una imagen. Si uno de los dos debe ser una imagen, usa image(<color>) para unir el valor de color.

Detección de temas basada en JS

La mayor parte de la ramificación del esquema de colores debe realizarse en CSS, que se adapta automáticamente a los cambios.

Cualquier JS que lea matchMedia("(prefers-color-scheme: dark)").matches TAMBIÉN DEBE controlar su evento change, ya que la preferencia del sistema puede cambiar en cualquier momento.

OPCIONAL: Implementa un botón de activación de esquema de colores

Si quieres permitir que los usuarios anulen de forma manual la configuración predeterminada del sistema para este sitio, puedes implementar un botón de activación del modo oscuro. La mayoría de los sitios no necesitan un botón de activación persistente en la IU del sitio. Por lo general, basta con usar el esquema de colores predeterminado del sistema, de forma opcional, con un parámetro de configuración en una superficie de configuración independiente. Consulta Botones de activación del modo oscuro para conocer las prácticas recomendadas para implementar un botón de activación del modo oscuro (persistente o no).

OPCIONAL: Anulaciones específicas de componentes

Puedes anular el tema global para elementos específicos configurando color-scheme en ellos.

Para obtener más información y conocer las trampas, consulta Temas claros y oscuros específicos de los componentes.

Problemas conocidos que debes tener en cuenta

Problemas que debes tener en cuenta cuando usas color-scheme

  • Chrome y Firefox respetan color-scheme para los iframes: renderizan las páginas incorporadas en el esquema de colores correcto y ajustan la consulta de medios prefers-color-scheme de la página incorporada para reflejar el color-scheme del contexto de incorporación. Safari no lo hace y resuelve prefers-color-scheme según la configuración del sistema, incluso dentro de los iframes.
    • Si controlas tanto el elemento superior como el iframe, pasa el esquema de colores del elemento superior al iframe de forma explícita, a través de un parámetro de URL (?theme=dark) en el momento de la construcción del iframe o a través de postMessage() (que también te permite reaccionar a los cambios en el tiempo de ejecución). En el iframe, establece una clase en <html> (o color-scheme en :root) a partir de ese indicador en lugar de depender de prefers-color-scheme.
    • Si solo controlas la página incorporada: No hay una forma confiable de detectar el color-scheme del contexto de incorporación desde el iframe en Safari. Expón un parámetro de tema explícito en tu API de incorporación (p.ej., una cadena de consulta o un protocolo postMessage) y documéntalo para los incorporadores.

Estrategias de resguardo

Resguardos y compatibilidad de navegadores con color-scheme

La propiedad color-scheme es una mejora progresiva. Los navegadores que no la admitan ignorarán esta propiedad y usarán su IU predeterminada de modo claro.

Para adaptarte a las preferencias del usuario en navegadores más antiguos, usa las consultas de medios prefers-color-scheme para proporcionar diferentes colores cuando se prefiera el modo oscuro.

  • SÍ: Usa la consulta de medios para cambiar las propiedades personalizadas en :root o html
  • Evita usar la consulta de medios en componentes individuales, a menos que el componente requiera un tipo muy específico de personalización del modo oscuro más allá de los colores.
: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);
}

Resguardos y compatibilidad de navegadores para light-dark()

En el caso de los navegadores que admiten color-scheme, pero aún no light-dark(), las versiones claras y oscuras de los colores se deben definir primero como propiedades personalizadas, y la consulta de medios prefers-color-scheme se debe usar para establecer los colores del modo correspondiente, como en el siguiente ejemplo:

: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);
}

Resguardos y compatibilidad de navegadores con los valores de imagen light-dark()

En el caso de los navegadores que no admiten light-dark() para las imágenes, usa image-set() como interruptor: asigna a cada opción un type() de una propiedad personalizada y establece la inactiva en un tipo de MIME no compatible para que el navegador descarte esa opción. La opción activa obtiene un valor vacío, por lo que es una opción image-set() simple sin type().

Define el interruptor una vez en :root. Luego, cada sitio de uso solo necesita ambas imágenes intercaladas, 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));
  }
}
  • Solo se descarga la imagen seleccionada.
  • Las resoluciones siguen funcionando: 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)).
  • NO uses type("") para la opción activa. Los navegadores tratan la cadena vacía como no compatible y descartan todas las opciones.
  • A diferencia de light-dark(), el interruptor no sigue el color-scheme establecido en un subárbol. Un interruptor manual o cualquier subárbol que establezca color-scheme TAMBIÉN DEBE activar --light-type y --dark-type.

Alternativa: una propiedad personalizada por imagen

Es más sencillo cuando solo varían unas pocas imágenes, pero cada una necesita su propia rama de consulta de medios, y un botón de activación manual debe cambiar cada una.

: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);
}

Resguardos y compatibilidad de navegadores para image()

Usa un gradiente con un solo punto de color y sin posiciones, p.ej., linear-gradient(blue).

Para una mayor compatibilidad con navegadores, puedes proporcionar de forma explícita posiciones de 0 100% para el elemento stop de un solo color, p.ej., linear-gradient(blue 0 100%).

Resguardos y compatibilidad de navegadores con scrollbar-color

Esta función es una mejora progresiva y no siempre requiere resguardos.

Si el diseño es importante y el objetivo de Baseline del usuario es "Baseline Widely Available" o anterior, DEBERÍAS incluir los seudoelementos ::-webkit-scrollbar no estándar como resguardos.

Une los resguardos heredados en un bloque @supports not (scrollbar-color: auto) para evitar conflictos entre las propiedades estándar y los selectores de WebKit heredados en los navegadores que admiten ambos de forma nativa.

Si usas propiedades personalizadas para definir colores, estos se aplicarán en cascada a los selectores de WebKit heredados automáticamente. NO es necesario que los dupliques.

/* 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);
  }
}

Resguardos y compatibilidad de navegadores con accent-color

La propiedad accent-color es una mejora progresiva. Los navegadores que no admitan esta propiedad la ignorarán y usarán los colores predeterminados de su IU.