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.
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.
<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 atributo | Efecto |
|---|---|
<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 | El fieldset de botones de opción; sin el botón, una fila de opciones. |
.luz-theme-mode, .luz-theme-mode, .luz-theme-mode, .luz-theme-mode | 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
| Nombre | Qué 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--fixedal botón fijo y no lo añade coninline. 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: moredahigh-contrast; si no,prefers-color-scheme: darkdadark; si no,light. - Qué escribe en
<html>: el modo endata-modey, sin marca, el mismo valor endata-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 adata-mode. - La elección se recuerda en
luz-theme, enlocalStorage; Automático la elimina yno-rememberlo desactiva. Si el script de la cabecera ya ha escritodata-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ñadescroll-padding-block-enda<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:
<head>
<meta charset="utf-8">
<script src="/theme-boot.min.js"></script>
<link rel="stylesheet" href="…">
</head>
- Qué escribe:
data-modeen<html>ydata-themecon el mismo valor, salvo que la página nombre una marca. El modo es el guardado enluz-themeo, 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 enscript-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
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.
| Parte | Color sobre el fondo | Claro | Oscuro | Alto contraste | Requisitos |
|---|---|---|---|---|---|
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-ringen la página y--luz-sidebar-text-hoveren 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 esButtonTexty se elimina la sombra. - Área interactiva: cada opción mide al menos 28 px de alto; el botón redondo mide
--luz-control-lgde 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.
- Color
--luz-brand-fill,--luz-brand-primary,--luz-brand-subtle,--luz-divider,--luz-focus-ring,--luz-outline,--luz-sidebar-text-hover,--luz-surface,--luz-surface-hover,--luz-text-on-fill,--luz-text-secondary- 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.