Interruptor de tema de interfaz
1) Principios
1. Sistema> skin. El tema no es solo una inversión de fondo, sino un conjunto de fichas (color, fondo, contraste, sombras, estados, ilustraciones, gráficos).
2. System-first. Por defecto, Sistema ('prefers-color-scheme') con la opción de seleccionar manualmente Light/Dark/High-Contrast.
3. Contraste predeterminado. El objetivo es WCAG AA, para texto pequeño/etiquetas importantes - AAA.
4. No hay brotes. Aplicamos el tema a render (inline-script), y hacemos transiciones suavemente.
5. Estabilidad de marca. Los acentos y la semántica de los estados se conservan en todos los temas.
2) Modos y escenarios
Light - scripts/formularios de pago/lectura larga.
Dark - noche/baja iluminación/partidos en vivo; reduce el brillo.
Sistema - siga el sistema operativo/navegador ('prefers-color-scheme').
High-Contrast - mayor contraste y minimización de la decoración (incluido el movimiento de reducción).
Seasonal/Promo (opcional) - encima del tema básico para el torneo/evento (no rompe tokens).
3) Arquitectura de tokens
Almacenamos tokens semánticos, no colores rectos:css
:root {
/ semantics/
--bg: hsl(0 0% 99%);
--bg-elev: hsl(0 0% 100%);
--fg: hsl(220 15% 15%);
--muted: hsl(220 10% 45%);
--accent: hsl (260 95% 60%) ;/brand accent/
--success: hsl(152 55% 40%);
--warning: hsl(36 85% 45%);
--danger: hsl(0 75% 50%);
--border: hsl(220 10% 90%);
--focus: hsl(260 95% 60% /.6);
--shadow: 0 6px 24px hsl(220 20% 10% /.08);
/ typography/radii/
--radius: 12px;
--lh: 1. 4;
}
:root[data-theme="dark"]{
--bg: hsl(220 18% 10%);
--bg-elev: hsl(220 18% 14%);
--fg: hsl(0 0% 96%);
--muted: hsl(220 10% 70%);
--accent: hsl (260 95% 65%) ;/slightly lighter in the dark/
--border: hsl(220 14% 22%);
--shadow: 0 8px 28px hsl(220 50% 2% /.6);
}
:root[data-theme="hc"]{
/ high-contrast: simple pairs, high Lc/
--bg: #000; --bg-elev:#000; --fg:#fff; --muted:#fff;
--accent:#00E; --success:#0F0; --warning:#FF0; --danger:#F00;
--border:#fff; --focus:#0FF;
}
Regla: los componentes sólo utilizan tokens semánticos.
4) Detector y guardar la selección
html
<script>
(function(){
const saved = localStorage. getItem('theme'); // 'light' 'dark' 'hc' 'system' null const sys = window. matchMedia('(prefers-color-scheme: dark)'). matches? 'dark': 'light';
const theme = saved && saved!=='system'? saved: sys;
document. documentElement. setAttribute('data-theme', theme);
})();
</script>
Interruptor UI: 'Light/Dark/System/High-Contrast'. Cuando seleccione 'System', no almacene un color específico, sólo una marca. Escuche los cambios en el sistema operativo:
js matchMedia('(prefers-color-scheme: dark)'). addEventListener('change', e=>{
if(localStorage. getItem('theme')==='system'){
document. documentElement. setAttribute('data-theme', e. matches? 'dark': 'light');
}
});
5) Transiciones suaves sin FOUC
Aplique el tema antes de descargar CSS (script en línea).
Las animaciones del tema son cortas y sólo 'color/background/border-color' (120-200 ms), pero no en el primer renderizado:css
@media (prefers-reduced-motion: no-preference){
html. theme-ready { transition: color. 18s, background-color. 18s, border-color. 18s; }
}
Después de montar la aplicación, agregue 'class =' theme-ready ''.
6) Componentes y estados
Texto/iconos: contraste ≥ AA; texto secundario no inferior a 4. 5:1 (en la dársena se «desvanece» fácilmente).
Campos/tarjetas: fondo '--bg-amb', borde '--border', sombra '--shadow'.
CTA: fondo '--accent', texto contrastado ('# fff' o calculado).
Estados (hover/focus/active/disabled): cambia el brillo/alfa en lugar de «desbordar el arco iris».
Gráficos/sparklines: paletas separadas para luz/oscuridad; la malla/los ejes son de bajo contraste, pero legibles.
7) Imágenes/medios/logotipos
Los iconos son monocromáticos - a través de 'currentColor' (ajustados al texto).
Los logotipos de las marcas no inviertan; preparar dos versiones (light/dark).
Carteles/capturas de pantalla: fácil overlay en dark (8-12%) para la legibilidad de los textos.
SVG: evite rellenos «duros», use vars 'var (--fg) '/' var (--accent)'.
8) Disponibilidad
Alto contraste: preset separado 'data-theme =' hc'.
Anillos de enfoque: siempre visibles ('outline: 2px solid var (--focus); outline-offset: 2px`).
No confíe en el color. Icono/texto/patrón para los estados.
Fuentes: 'font-variant-numeric: tabular-nums;' para sumas/coffes.
RTL: el tema no rompe el espejado (usamos propiedades lógicas).
9) Performance
Colores - Propiedades personalizadas CSS en la raíz → reutilización instantánea sin render componentes.
Evite «repintar» imágenes con filtros 'invert ()' en contenedores grandes.
Sustitución perezosa de las ilustraciones pesadas para el tema (si es necesario).
No almacene paletas grandes en JS: el tema está controlado por la clase/atributo.
10) Especificidad de iGaming
Coffs en vivo por la noche: contraste «suave» (AAA para números), resaltar el cambio de coeficiente es discreto, sin parpadeos.
Juego responsable: los recordatorios y las pistas son legibles en ambos temas; sin flores «venenosas» por la noche.
Caja registradora: campos/firmas/errores sin acentos luminosos fatigosos; éxito/error son estables en el tema.
«Skins» del torneo: sólo como accent-override superficial, no rompa los tokens básicos.
11) Snippets de IU
Interruptor (HTML/JS):html
<label class="theme-switch">
<span> Topic </span>
<select id="theme">
<option value = "system "> System </option>
<option value = "light "> Bright </option>
<option value = "dark "> Dark </option>
<option value = "hc"> High contrast </option>
</select>
</label>
<script>
const sel = document. getElementById('theme');
sel. value = localStorage. getItem('theme') 'system';
sel. addEventListener('change', e=>{
const v = e. target. value;
localStorage. setItem('theme', v);
if(v==='system'){
const sys = matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light';
document. documentElement. setAttribute('data-theme', sys);
} else {
document. documentElement. setAttribute('data-theme', v);
}
});
</script>
Presets de componentes:
css
.btn{height:44px; padding:0 16px; border-radius:var(--radius); display:inline-flex; align-items:center; gap:8px}
.btn--primary{background:var(--accent); color:#fff}
.card{background:var(--bg-elev); border:1px solid var(--border); box-shadow:var(--shadow); border-radius:var(--radius)}
.text-muted{color:var(--muted)}
React hook (persist + system):
tsx import { useEffect, useState } from 'react';
type Theme = 'light' 'dark' 'hc' 'system';
export function useTheme(){
const [theme, setTheme] = useState<Theme>(()=>localStorage. getItem('theme') as Theme 'system');
useEffect(()=>{
const apply = (t:Theme)=>{
const v = t==='system'? (matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light'): t;
document. documentElement. setAttribute('data-theme', v);
};
apply(theme);
const mql = matchMedia('(prefers-color-scheme: dark)');
const on = ()=> theme==='system' && apply('system');
mql. addEventListener('change', on);
return ()=> mql. removeEventListener('change', on);
},[theme]);
useEffect(()=> localStorage. setItem('theme', theme),[theme]);
return { theme, setTheme };
}
12) Métricas
Theme adoption rate: proporción de usuarios en Dark/System/HC.
Tasa FOUC: fracción con «ráfaga blanca» visible al inicio (<1%).
Contrast defects: número de errores de contraste por versión.
Error visibility: clics/repeticiones debido a errores «imperceptibles» en diferentes temas.
Impacto energético (mobile): comparación del tiempo de sesión en darka vs ladrar (métrica indirecta).
13) Anti-patrones
Invertir todo 'filter: invert (1)' - rompe marca y significados.
Cambiar los colores directamente en los componentes sin tokens.
Esconder los anillos de enfoque en un tema oscuro.
Texto demasiado oscuro sobre fondo oscuro (o claro sobre claro).
Transición larga a toda la página (repreguntas).
Colores «exclusivos» de los estados en un tema que no están en otro.
14) Lista de comprobación de QA
Contraste y legibilidad
- Todos los textos ≥ AA; etiquetas críticas/texto pequeño ≥ AAA.
- Los errores/aciertos/advertencias son distinguibles no sólo por el color.
la Conducta
- El sistema respeta 'prefers-color-scheme' y reacciona al cambio de OS.
- No FOUC (el tema se aplica a render).
- Cambiar tema no restablece el estado de las páginas.
los Componentes
- Las tarjetas/formularios/tablas sólo utilizan tokens.
- Los gráficos tienen paletas para ambos temas.
- Los logotipos/iconos se ven correctamente en ambos temas.
A11y
- Focus-ring visible; El modo de alto contraste está disponible.
- Se tiene en cuenta 'prefers-reduced-motion'.
Performance
- Transición ≤ 200 ms; sin reflow global.
- No hay filtros/mezclas pesadas en los contenedores.
15) Documentación en el sistema de diseño
Temas: paletas, contrastes, estado (hover/focus/active/disabled).
Guías: cómo agregar un nuevo acento de marca sin retroceso de contraste.
Charts/Media: paletas prediseñadas para luz/oscuridad.
Patterns: System-first, High-Contrast, conmutación suave sin FOUC.
Do/Don 't: inversión por filtro, color en línea, errores invisibles/enfoque.
Resumen breve
El interruptor de tema de trabajo es tokens semánticos + Sistema-primero + inicio inmersivo. Fijar el contraste, centralizar los colores, respetar 'prefers-color-scheme' y reducir la motion, almacenar la elección del usuario y evitar efectos pesados. A continuación, IU sigue siendo legible y reconocible en cualquier entorno, desde un partido en vivo nocturno hasta una taquilla diurna y pantallas de torneos.