Saltar al contenido principal

Core · Navegación

Modo de color

Permite elegir Automático, claro, oscuro o alto contraste: con botones de opción que funcionan sin JavaScript o con un botón redondo que los recorre.

Esta traducción la ha escrito una IA y todavía no la ha revisado ninguna persona. Leer esta página en inglés

Ejemplo

El grupo de botones de opción, el botón redondo fijo en la esquina final de su escenario y el mismo botón en el flujo. Son imágenes de los estados: no están dentro de ningún elemento y son inertes, así que no cambian esta página. El botón que funciona es el de la esquina final de esta ventana.

Radio group

Button, fixed at the end corner

Button, inline

Without JavaScript the radios show in the flow, as in the first stage, and there is no button.

Problemas conocidos

Qué puedes esperar:

  • Sin el script de la cabecera, una persona con modo oscuro ve primero la página clara durante un instante: el elemento se ejecuta después del primer pintado.
  • Automático sigue solo los ajustes del sistema. Para que quien lee pueda elegir también la hora del día, añade <luz-theme-time>, del catálogo.
  • El texto es tuyo: LUZ no escribe ninguna etiqueta, así que traduce la leyenda y los modos con la página.
  • No se ha probado con un lector de pantalla.

Código

Copiar el marcado

Escribe un botón de opción por cada elección dentro de <luz-theme-mode>, en el orden en que el botón los recorre. Pon un icono SVG (currentColor, aria-hidden) en cada etiqueta: el botón muestra el de la opción seleccionada. La leyenda y las etiquetas son tuyas.

HTML
<script type="module" src="https://cdn.jsdelivr.net/npm/@luz-ds/core@0.3.0/dist/luz.min.js"></script>

<luz-theme-mode>
  <fieldset class="luz-theme-mode__group">
    <legend class="luz-theme-mode__legend">Color mode</legend>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="auto" checked><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>Auto</label>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="light"><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>Light</label>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="dark"><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>Dark</label>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="high-contrast"><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>High contrast</label>
  </fieldset>
</luz-theme-mode>

luz.min.js carga theme-mode.min.js cuando la página tiene el elemento. Para cargarlo tú, o para un elemento añadido cuando la página ya ha cargado, import "@luz-ds/core/theme-mode";.

Variantes y opciones

Clase o atributoEfecto
<luz-theme-mode>
El elemento. Contiene los botones de opción; con scripting activado añade un botón redondo, fijo en la esquina final.
inline
Un booleano. El botón se queda donde lo pone el marcado: un pie de página, una barra de navegación, una barra lateral.
no-remember
Un booleano. La elección no se lee ni se guarda en luz-theme: la página empieza en Automático.
name="luz-theme-mode"
Mantenlo: sin JavaScript, los tokens lo leen para cambiar el modo.
value
auto, light, dark o high-contrast. El botón recorre los botones de opción en el orden del marcado y se salta los desactivados.
.luz-theme-mode__group
El fieldset de botones de opción; sin el botón, una fila de opciones.
.luz-theme-mode__legend, .luz-theme-mode__option, .luz-theme-mode__input, .luz-theme-mode__icon
La leyenda (da nombre al grupo y, con la etiqueta seleccionada, al botón); una etiqueta con su botón de opción, su icono y su nombre; el botón de opción, oculto a la vista pero con foco; el icono, que el botón copia de la opción seleccionada.
__button, __button--fixed, __status
Los escribe el script, no tú: el botón redondo (fijo salvo con inline) y una región dinámica oculta que dice el nuevo modo.

Propiedades, métodos y evento

NombreQué hace
el.choice
La elección (auto, light, dark, high-contrast). Al asignarla se elige ese botón de opción; cualquier otro valor se ignora.
el.mode
El modo en uso. En Automático es el del sistema.
el.refresh()
Aplica el modo de nuevo. Llámalo después de cambiar lo que significa Automático.
el.autoMode
Una función que devuelve light, dark o nada (el del sistema). Otro script la asigna para decidir qué significa Automático, como hace <luz-theme-time>; el alto contraste del sistema sigue ganando.
luz-theme-mode:change
Sube por el árbol tras la mejora y en cada cambio. detail: mode, auto (la elección es Automático) y user (la ha elegido una persona).

Cómo funciona

Un grupo nativo de botones de opción con estilos de components/theme-mode.css. Los botones de opción son el estado; con scripting activado, el elemento los convierte en un botón.

  • El botón: cada pulsación marca el siguiente botón de opción y lo aplica; el grupo se oculta mientras está el botón. El script añade luz-theme-mode__button--fixed al botón fijo y no lo añade con inline. Un botón de opción con foco se lo pasa al botón.
  • Automático sigue el sistema, también mientras la página está abierta: prefers-contrast: more da high-contrast; si no, prefers-color-scheme: dark da dark; si no, light.
  • Qué escribe en <html>: el modo en data-mode y, sin marca, el mismo valor en data-theme, que es el que leen los tokens. Una página que nombra una marca (data-theme="my-brand", un tema propio hecho a partir de _template.css) la conserva, y los bloques de la marca responden a data-mode.
  • La elección se recuerda en luz-theme, en localStorage; Automático la elimina y no-remember lo desactiva. Si el script de la cabecera ya ha escrito data-mode, el primer Automático lo adopta. Un segundo <luz-theme-mode> sigue al primero.
  • Dónde está el botón: salvo con inline, en la esquina final de la ventana (abajo a la izquierda en una página de derecha a izquierda), en --luz-z-raised: por encima del contenido y por debajo de la barra lateral, los diálogos y los avisos.
  • Deja libre la esquina. Dale al pie de página un relleno final de calc(var(--luz-control-lg) + var(--luz-space-md) * 2) dentro de @media (scripting: enabled). La hoja de estilos añade scroll-padding-block-end a <html>, así que con Tab ningún elemento con foco queda bajo el botón; con <luz-cookie-consent> visible, el botón se coloca sobre el banner.
  • En una barra lateral toma los colores de la barra lateral del catálogo; en otra superficie oscura, define las propiedades --luz-theme-mode-*.

Sin JavaScript

Los cuatro botones de opción se muestran donde los pone el marcado; no hay botón. Elegir uno cambia el modo, porque los tokens leen el botón de opción seleccionado (:root:has()), mientras <html> no tenga un data-mode. Funciona también con un tema de marca, si su _template.css es el actual. La elección no se recuerda y Automático sigue al sistema: oscuro con prefers-color-scheme: dark, alto contraste con prefers-contrast: more y claro en los demás casos.

Teclado y ARIA

  • Botón: se llega a él con Tab; Enter o Espacio pasa al modo siguiente. El foco sigue el marcado, no la esquina.
  • Nombre y estado: el nombre del botón es el texto de la leyenda, dos puntos y la etiqueta de la opción seleccionada, como «Modo de color: Oscuro». Después de que una persona elija, una región oculta con role="status" dice lo mismo; no dice nada cuando carga la página.
  • Botones de opción: sin el botón, con Tab se llega al grupo y las teclas de dirección eligen un modo.

Script para la cabecera

Opcional. Establece el modo antes del primer pintado, así que una persona con modo oscuro nunca ve primero la página clara. Copia node_modules/@luz-ds/core/dist/theme-boot.min.js a tu propio origen y enlázalo en el <head>, antes de las hojas de estilos:

HTML
<head>
  <meta charset="utf-8">
  <script src="/theme-boot.min.js"></script>
  <link rel="stylesheet" href="…">
</head>
  • Qué escribe: data-mode en <html> y data-theme con el mismo valor, salvo que la página nombre una marca. El modo es el guardado en luz-theme o, si no, el del sistema (como Automático, arriba). Nunca escribe en el almacenamiento.
  • Por qué bloquea el renderizado: es un script clásico, no un módulo, de unos cientos de bytes: el navegador lo ejecuta antes del primer pintado. Es la única excepción documentada a no usar JavaScript en la ruta crítica (EX-009), y cada sitio decide si lo usa. Sin él, el elemento escribe los mismos atributos después del primer pintado, así que una persona con modo oscuro ve la página clara durante un instante.
  • Tu propio origen: con una Content-Security-Policy (CSP) sin 'unsafe-inline', sirve el archivo desde tu propio origen. Un script bloqueante de otro servidor retrasa el primer pintado y necesita ese servidor en script-src.

Amplíalo con el catálogo: momentos del día y la barra

<luz-theme-time>, de @luz-ds/catalog, envuelve a este elemento. Añade la hora del día como una elección, en los momentos que declares (amanecer, mañana, atardecer), y puede añadir un deslizador de las 24 horas. Automático sigue siendo el del sistema. Este elemento no lo necesita: si el catálogo no carga, el botón funciona como aquí.

El contraste y el foco están en la pestaña Diseño.

Diseño

Cuándo usarlo

Ofrece un control del modo de color en todas las páginas del sitio, siempre en el mismo lugar.

  • El botón redondo en la esquina final, una vez por página.
  • El botón redondo con inline, cuando forma parte de un panel: un pie de página, una barra de navegación, una barra lateral.

Cuándo no usarlo

No pongas un segundo botón fijo en una página: los demás son en línea.

  • No lo fijes en una esquina que ocupa otro control, como un botón de chat.
  • No dejes los botones de opción fuera del marcado: sin JavaScript son el único control.
  • No lo uses para elegir una marca: una marca se nombra con data-theme; este elemento elige su modo.

Qué hacer y qué no

Hazlo Nombra los modos según lo que son: «Automático», «Claro», «Oscuro» y «Alto contraste».
No lo hagas Llamar «Accesible» al modo de alto contraste: da a entender que los otros no lo son y no explica qué cambia.
Hazlo Un único botón fijo en la esquina final, con los botones de opción dentro del elemento para quien no tiene JavaScript.
No lo hagas Poner un segundo control fijo en la misma esquina: se tapan entre sí y uno queda oculto cuando el otro está encima.

Cómo escribir el contenido

Llama al grupo «Modo de color» y a las opciones «Automático», «Claro», «Oscuro» y «Alto contraste».

  • Cada opción conserva su nombre visible junto a su icono, así que los botones de opción se leen sin los iconos.
  • El nombre del botón es la leyenda más la etiqueta seleccionada, «Modo de color: Oscuro». Escribe ambas de modo que se lean bien juntas y tradúcelas con la página: LUZ no escribe nada de este texto.
  • Di cerca del control qué sigue Automático: «Automático sigue tu dispositivo».

Contraste y foco

En la página, la opción seleccionada aparece rellena con el color de marca; en la barra lateral usa los colores del enlace actual de esa barra. El botón redondo es una superficie con su icono: el modo seleccionado es su icono, nunca un relleno.

ParteColor sobre el fondoClaroOscuroAlto contrasteRequisitos
Etiqueta del grupo
--luz-text-secondary on --luz-surface-page
6.2:1
7.5:1
19.3:1
4.5:1
Nombre de la opción, al pasar el cursor
--luz-text-primary on --luz-surface-hover
14.4:1
12.6:1
20.1:1
4.5:1
Borde de la opción
--luz-outline on --luz-surface-page
4.3:1
3.6:1
21.0:1
3:1
Icono del botón redondo
--luz-text-secondary on --luz-surface
6.4:1
6.9:1
19.3:1
3:1
Icono del botón redondo, al pasar el cursor
--luz-text-secondary on --luz-surface-hover
5.6:1
6.2:1
18.4:1
3:1
Opción seleccionada
--luz-text-on-fill on --luz-brand-fill
6.0:1
4.9:1
14.1:1
4.5:1
Relleno de la opción seleccionada
--luz-brand-fill on --luz-surface-page
5.6:1
3.5:1
14.1:1
3:1
Anillo de foco
--luz-focus-ring on --luz-surface-page
5.6:1
7.7:1
21.0:1
3:1
Etiqueta del grupo, en la barra lateral
--luz-sidebar-label on --luz-sidebar-bg
6.7:1
6.6:1
6.4:1
4.5:1
Opción seleccionada, en la barra lateral
--luz-sidebar-active-text on --luz-sidebar-active-bg
10.3:1
10.2:1
21.0:1
4.5:1
Relleno de la opción seleccionada, en la barra lateral
--luz-sidebar-active-bg on --luz-sidebar-bg
8.9:1
10.2:1
21.0:1
3:1
Anillo de foco, en la barra lateral
--luz-sidebar-text-hover on --luz-sidebar-bg
15.7:1
18.9:1
21.0:1
3:1

Medido a partir de los valores de los tokens de cada modo, redondeado hacia abajo. Los requisitos son los mínimos de WCAG 2.2 AA: 4,5:1 para el texto y 3:1 para los anillos de foco, los bordes y los iconos.

  • Foco: un contorno de 2 px, a 2 px de la opción completa o del botón redondo, con --luz-focus-ring en la página y --luz-sidebar-text-hover en la barra lateral.
  • No solo color: cada opción conserva su nombre visible; con colores forzados, la seleccionada usa el color de resaltado del sistema y un borde de 2 px, y una desactivada tiene un borde discontinuo.
  • Borde: la línea fina del botón redondo (--luz-divider) es un adorno: lo que distingue el control es el icono, con 3:1 o más. Con colores forzados el borde es ButtonText y se elimina la sombra.
  • Área interactiva: cada opción mide al menos 28 px de alto; el botón redondo mide --luz-control-lg de ancho y de alto.
  • Sin taparse: la página deja libre la esquina en su final y desplaza un elemento con foco para sacarlo de debajo del botón (WCAG 2.4.11).

Tokens que usa

Los 22 tokens que lee components/theme-mode.css, agrupados por familia. Cada familia enlaza a su página, con los valores de cada modo. En Figma son variables con los mismos nombres: consulta Tokens en Figma.

Espaciado y tamaños
--luz-control-lg, --luz-space-2xs, --luz-space-md, --luz-space-xs
Tipografía
--luz-font-mono, --luz-text-xs
Radio
--luz-radius-full, --luz-radius-md
Sombras
--luz-shadow-sm
Movimiento
--luz-duration-fast
Capas
--luz-z-raised

El teclado y ARIA están en la pestaña Código.