Contents
Introducción
Este artículo explica paso a paso cómo implementar un selector claro/oscuro en WordPress y cómo guardar la preferencia del usuario mediante JavaScript para que persista entre páginas y sesiones. Se incluyen buenas prácticas: uso de variables CSS, detección de la preferencia del sistema, prevención del parpadeo (FOIT), accesibilidad y la integración correcta dentro de un tema o plugin de WordPress.
Requisitos y objetivos
- Compatibilidad con navegadores modernos (soporte para localStorage y prefers-color-scheme).
- Aplicar temas mediante variables CSS (fácil mantenimiento y transiciones suaves).
- Persistencia de la preferencia con localStorage (alternativa: cookies).
- Evitar parpadeo inicial mostrando el tema correcto desde el primer render.
- Integración con el flujo de WordPress (enqueue de scripts y estilo, impresión de un script inline en ltheadgt).
Conceptos clave
- Variables CSS: permiten cambiar colores globalmente sin duplicar reglas.
- data-theme en el elemento raíz (document.documentElement): selector claro para estilos [data-theme=dark].
- prefers-color-scheme: detectar la preferencia del sistema como valor por defecto.
- localStorage: almacenar la preferencia del usuario de forma persistente.
- Script inline en ltheadgt: aplicar la preferencia antes de que se cargue CSS para evitar FOIT.
Implementación paso a paso
1) CSS: definir variables y reglas para los temas
Se recomienda usar variables en :root para el tema claro y sobreescribirlas cuando data-theme=dark esté presente en el elemento raíz. Así se evita tocar muchas reglas y se mantiene el control centralizado.
:root {
--bg: #ffffff
--text: #111827
--link: #1a73e8
--muted: #6b7280
--accent: #2563eb
--transition-speed: 200ms
}
[data-theme=dark] {
--bg: #0b1220
--text: #e6eef8
--link: #8ab4ff
--muted: #9aa4b2
--accent: #60a5fa
}
/ Usar las variables en el CSS del tema /
:root, [data-theme] {
background-color: var(--bg)
color: var(--text)
transition: color var(--transition-speed) ease, background-color var(--transition-speed) ease
}
a {
color: var(--link)
}
.small-muted {
color: var(--muted)
}
2) HTML del control (marcado accesible)
Un botón simple con atributos ARIA para indicar estado puede colocarse en la cabecera o en el personalizador del tema.
3) JavaScript: lógica para alternar y persistir preferencia
Se compone de dos partes: un script inline que se imprime en ltheadgt para aplicar el tema antes del render y un script principal que maneja el evento del botón y guarda la preferencia en localStorage.
Script inline para evitar FOIT (añádelo en ltheadgt mediante wp_head o print_inline_script):
(function() {
try {
var stored = localStorage.getItem(wp-theme-preference)
if (stored === dark stored === light) {
document.documentElement.setAttribute(data-theme, stored)
return
}
var prefersDark = window.matchMedia window.matchMedia((prefers-color-scheme: dark)).matches
if (prefersDark) {
document.documentElement.setAttribute(data-theme, dark)
} else {
document.documentElement.setAttribute(data-theme, light)
}
} catch (e) {
// Si localStorage no está disponible, confiar en la preferencia del sistema (no hacer nada)
}
})()
Script principal (archivo JS separado):
(function() {
var toggle = document.getElementById(theme-toggle)
if (!toggle) return
function getStoredTheme() {
try {
return localStorage.getItem(wp-theme-preference)
} catch (e) {
return null
}
}
function storeTheme(theme) {
try {
localStorage.setItem(wp-theme-preference, theme)
} catch (e) {
// silenciar errores de almacenamiento
}
}
function applyTheme(theme) {
if (!theme) return
document.documentElement.setAttribute(data-theme, theme)
if (toggle) {
toggle.setAttribute(aria-pressed, theme === dark ? true : false)
// Actualizar icono si lo deseas
var icon = document.getElementById(theme-toggle-icon)
if (icon) icon.textContent = theme === dark ? 🌙 : ☀️
}
}
// Inicializar estado del botón según lo almacenado o preferencia del sistema
(function init() {
var stored = getStoredTheme()
if (stored) {
applyTheme(stored)
} else {
var prefersDark = window.matchMedia window.matchMedia((prefers-color-scheme: dark)).matches
applyTheme(prefersDark ? dark : light)
}
})()
toggle.addEventListener(click, function() {
var current = document.documentElement.getAttribute(data-theme) light
var next = current === dark ? light : dark
applyTheme(next)
storeTheme(next)
// Si usas analytics o eventos, puedes enviar aquí el cambio
})
})()
4) Integración en WordPress (enqueue y script inline en ltheadgt)
Coloca este código en functions.php de tu tema o en un plugin específico del sitio. El objetivo es:
- Encolar la hoja de estilos que usa las variables CSS.
- Encolar el archivo JS principal.
- Imprimir el script inline (el pequeño de inicialización) en ltheadgt para prevenir FOIT.
// En functions.php o en un plugin
function mi_tema_enqueue_theme_toggle() {
// Encolar CSS del tema (ajusta handle y ruta)
wp_enqueue_style(mi-tema-style, get_stylesheet_uri(), array(), wp_get_theme()->get(Version))
// Encolar script principal depender de jquery es opcional
wp_enqueue_script(
mi-tema-theme-toggle,
get_template_directory_uri() . /assets/js/theme-toggle.js,
array(),
1.0,
true
)
// Obtener el script small que aplicará el tema antes de renderizar
inline = (function(){try{var s=localStorage.getItem(wp-theme-preference)if(s===darks===light){document.documentElement.setAttribute(data-theme,s)return}var p=window.matchMediawindow.matchMedia((prefers-color-scheme: dark)).matchesif(p){document.documentElement.setAttribute(data-theme,dark)}else{document.documentElement.setAttribute(data-theme,light)}}catch(e){}})()
// Imprimir inline en head
wp_register_script(mi-tema-inline-theme, )
wp_enqueue_script(mi-tema-inline-theme)
wp_add_inline_script(mi-tema-inline-theme, inline)
}
add_action(wp_enqueue_scripts, mi_tema_enqueue_theme_toggle)
Notas:
- Si usas un constructor o plugin de caché, asegúrate de que el script inline no sea eliminado ni combinado de forma que deje de ejecutarse antes del CSS.
- El script inline debe cargarse lo más pronto posible (antes del CSS) para evitar que se muestre el tema incorrecto al cargar la página.
Accesibilidad y detalles UX
- Usa atributos ARIA: aria-pressed en el botón para que lectores de pantalla puedan interpretar el estado.
- Asegura contraste suficiente en ambos temas (contrasta texto y fondo según WCAG).
- Ofrece prefers-reduced-motion para usuarios que no desean transiciones largas.
- Permite teclado: el botón debe ser alcanzable con Tab y activable con Enter/Espacio.
- Si tu sitio tiene persistencia de sesión en servidor (por ejemplo, perfiles de usuario), puedes sincronizar la preferencia guardándola en meta del usuario vía AJAX y restaurándola por PHP para usuarios autenticados.
Variantes y opciones avanzadas
- Sin JavaScript: imposible cambiar dinámicamente, pero puedes usar media query prefers-color-scheme para servir estilos por defecto.
- Guardar en PHP (usuarios autenticados): guardar meta user_meta y aplicar theme en servidor para que el HTML generado ya tenga el atributo correcto.
- Usar cookies en lugar de localStorage: útil si quieres que el valor también esté disponible en servidores o plugins que leen cookies.
- Clases en lugar de data-attribute: algunas integraciones prefieren añadir una clase como .theme-dark al elemento raíz la técnica es equivalente.
Problemas comunes y soluciones
- Parpadeo inicial (FOIT): se soluciona con el script inline que aplica el tema antes de que se cargue el CSS.
- Caching/CDN: si un CSS generado depende del tema, mejor usar variables CSS y aplicar el atributo en HTML para evitar servir archivos distintos cached.
- Plugins que alteran el DOM o el head: asegúrate de que tu script inline se mantenga y no sea desplazado abajo por otros plugins imprimirlo con wp_add_inline_script es la mejor práctica.
- LocalStorage deshabilitado: el script contiene try/catch para comportarse de forma segura en estos casos y caer en la preferencia del sistema.
Código completo de ejemplo
A continuación, un conjunto de ejemplos completos para copiar/pegar y adaptar: CSS, JS principal y PHP para enqueue.
/ assets/css/theme-vars.css /
:root {
--bg: #ffffff
--text: #111827
--muted: #6b7280
--link: #1a73e8
--transition: 150ms
}
[data-theme=dark] {
--bg: #0b1220
--text: #e6eef8
--muted: #9aa4b2
--link: #8ab4ff
}
html, body {
background: var(--bg)
color: var(--text)
transition: background var(--transition) ease, color var(--transition) ease
}
button#theme-toggle {
background: transparent
border: 1px solid var(--muted)
color: var(--text)
padding: 6px 10px
border-radius: 6px
}
/ assets/js/theme-toggle.js /
(function() {
var toggle = document.getElementById(theme-toggle)
if (!toggle) return
function getStoredTheme() {
try { return localStorage.getItem(wp-theme-preference) } catch (e) { return null }
}
function storeTheme(theme) {
try { localStorage.setItem(wp-theme-preference, theme) } catch (e) {}
}
function applyTheme(theme) {
if (!theme) return
document.documentElement.setAttribute(data-theme, theme)
toggle.setAttribute(aria-pressed, theme === dark ? true : false)
var icon = document.getElementById(theme-toggle-icon)
if (icon) icon.textContent = theme === dark ? 🌙 : ☀️
}
(function init() {
var s = getStoredTheme()
if (s) {
applyTheme(s)
} else {
var pref = window.matchMedia window.matchMedia((prefers-color-scheme: dark)).matches
applyTheme(pref ? dark : light)
}
})()
toggle.addEventListener(click, function() {
var current = document.documentElement.getAttribute(data-theme) light
var next = current === dark ? light : dark
applyTheme(next)
storeTheme(next)
})
})()
/ functions.php: encolar CSS, JS y añadir script inline en head /
function mi_tema_enqueue_theme_toggle() {
// CSS con variables
wp_enqueue_style(mi-tema-vars, get_template_directory_uri() . /assets/css/theme-vars.css, array(), 1.0)
// Script principal
wp_enqueue_script(mi-tema-theme-toggle, get_template_directory_uri() . /assets/js/theme-toggle.js, array(), 1.0, true)
// Inline script para aplicar tema antes del render y evitar FOIT
inline = (function(){try{var s=localStorage.getItem(wp-theme-preference)if(s===darks===light){document.documentElement.setAttribute(data-theme,s)return}var p=window.matchMediawindow.matchMedia((prefers-color-scheme: dark)).matchesif(p){document.documentElement.setAttribute(data-theme,dark)}else{document.documentElement.setAttribute(data-theme,light)}}catch(e){}})()
// Registrar e inyectar inline
wp_register_script(mi-tema-inline, )
wp_enqueue_script(mi-tema-inline)
wp_add_inline_script(mi-tema-inline, inline)
}
add_action(wp_enqueue_scripts, mi_tema_enqueue_theme_toggle)
Conclusión
Con esta aproximación se obtiene un selector claro/oscuro robusto: uso de variables CSS para simplicidad y rendimiento, persistencia con localStorage, detección de la preferencia del sistema, y una integración segura en WordPress mediante enqueue y un script inline para evitar parpadeos. Implementando buenas prácticas de accesibilidad y comprobando la interacción con cachés y plugins, el resultado es una experiencia consistente y agradable para los usuarios.
|
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |
