Introducción
Objetivo: Mostrar un tutorial completo y detallado para implementar en un tema de WordPress un esquema de colores claro/oscuro aprovechando la media query prefers-color-scheme, y además ofrecer un conmutador manual para que el usuario pueda forzar una preferencia distinta a la del sistema. El artículo cubre CSS, JavaScript, integración en WordPress (functions.php y añadir el botón en el menú o header), accesibilidad y consideraciones de compatibilidad.
Resumen de la técnica
- Usar variables CSS (custom properties) para definir colores reutilizables.
- Aplicar por defecto el esquema claro en :root y usar @media (prefers-color-scheme: dark) para adaptar al modo oscuro del sistema.
- Permitir un override manual con un atributo (por ejemplo data-theme=dark) en el elemento raíz (html), para que el usuario pueda elegir y su elección se guarde en localStorage.
- Integrar el JavaScript y el CSS en WordPress mediante hooks apropiados y añadir un botón accesible para alternar.
Por qué usar variables CSS y prefers-color-scheme
- Variables CSS: centralizan paletas y facilitan mantenimiento y cambios por componentes o estados.
- prefers-color-scheme: permite respetar la preferencia del sistema operativo sin necesidad de JavaScript, ofreciendo una experiencia inmediata y coherente.
- Override con atributo: combina lo mejor de ambos mundos: la detección automática y el control manual persistente del usuario.
Preparar la estructura de variables
A continuación se muestra un ejemplo de cómo organizar variables para el tema. Colócalo en tu hoja de estilos principal (style.css o un partial SASS/LESS que compile al CSS final).
:root {
/ Paleta por defecto (modo claro) /
--color-bg: #ffffff
--color-surface: #f6f7f9
--color-text: #111213
--color-muted: #6b7280
--color-primary: #0b5fff
--color-border: #e6e9ef
/ Variables de elevación, sombras, transiciones /
--shadow-sm: 0 1px 3px rgba(16,24,40,0.06)
--transition-fast: 200ms ease
}
/ Si el sistema está en modo oscuro, sobreescribimos variables /
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0b0f12
--color-surface: #0f1316
--color-text: #e6eef6
--color-muted: #9aa6b2
--color-primary: #58a6ff
--color-border: rgba(255,255,255,0.06)
}
}
/ Override manual: cuando el usuario fuerza tema oscuro o claro /
html[data-theme=dark] {
--color-bg: #0b0f12
--color-surface: #0f1316
--color-text: #e6eef6
--color-muted: #9aa6b2
--color-primary: #58a6ff
--color-border: rgba(255,255,255,0.06)
}
html[data-theme=light] {
--color-bg: #ffffff
--color-surface: #f6f7f9
--color-text: #111213
--color-muted: #6b7280
--color-primary: #0b5fff
--color-border: #e6e9ef
}
/ Ejemplo de uso de variables en componentes /
.site {
background-color: var(--color-bg)
color: var(--color-text)
transition: background-color var(--transition-fast), color var(--transition-fast)
}
.header {
background: linear-gradient(180deg, var(--color-surface), var(--color-bg))
border-bottom: 1px solid var(--color-border)
box-shadow: var(--shadow-sm)
}
a {
color: var(--color-primary)
}
Botón toggle accesible: HTML y atributos ARIA
El botón debe ser accesible: elemento button con aria-pressed o aria-checked, título claro y foco visible. Inserta este markup en tu header.php o mediante un hook que lo imprima en la cabecera o dentro del menú.
CSS mínimo para el botón (estético y foco)
.theme-toggle {
background: transparent
border: 1px solid var(--color-border)
color: var(--color-text)
padding: 6px 10px
border-radius: 6px
cursor: pointer
transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast)
}
.theme-toggle:focus {
outline: 3px solid rgba(88,166,255,0.18)
outline-offset: 2px
}
JavaScript: lógica para preferencia, override y persistencia
La lógica que necesitas implementar:
- Al cargar la página, leer si hay una preferencia guardada en localStorage.
- Si existe, aplicar data-theme=dark o light en document.documentElement.
- Si no existe, respetar la preferencia del sistema (por defecto la media query ya la aplica, pero podemos sincronizar el botón con ella).
- Escuchar cambios en la media query para actualizar el estado del botón si el usuario no ha forzado preferencia manual.
- Al hacer clic en el botón, alternar el tema, guardar en localStorage y actualizar atributos ARIA.
(function () {
const storageKey = theme-preference // dark light null
const toggle = document.getElementById(theme-toggle)
const prefersDarkQuery = window.matchMedia((prefers-color-scheme: dark))
function applyTheme(theme) {
if (theme === dark) {
document.documentElement.setAttribute(data-theme, dark)
if (toggle) toggle.setAttribute(aria-pressed, true)
} else if (theme === light) {
document.documentElement.setAttribute(data-theme, light)
if (toggle) toggle.setAttribute(aria-pressed, false)
} else {
document.documentElement.removeAttribute(data-theme)
// Si queremos sincronizar el estado del botón con la preferencia del sistema:
const isDark = prefersDarkQuery.matches
if (toggle) toggle.setAttribute(aria-pressed, isDark ? true : false)
}
}
function getStoredPreference() {
try {
return localStorage.getItem(storageKey)
} catch (e) {
return null
}
}
function storePreference(value) {
try {
if (value === null) localStorage.removeItem(storageKey)
else localStorage.setItem(storageKey, value)
} catch (e) {
// Silenciar si localStorage no está disponible
}
}
// Inicialización: aplicar preferencia almacenada o dejar a la media query
const saved = getStoredPreference()
if (saved === dark saved === light) {
applyTheme(saved)
} else {
applyTheme(null) // respetar prefers-color-scheme
}
// Si no hay preferencia guardada, actualizar cuando cambie la media query
prefersDarkQuery.addEventListener
? prefersDarkQuery.addEventListener(change, (e) => {
if (!getStoredPreference()) {
applyTheme(null)
}
})
: prefersDarkQuery.addListener((e) => {
if (!getStoredPreference()) {
applyTheme(null)
}
})
// Toggle click
if (toggle) {
toggle.addEventListener(click, function () {
const current = getStoredPreference()
let next
if (current === dark) next = light
else if (current === light) next = dark
else {
// No hay preferencia guardada: invertimos la preferencia actual
next = prefersDarkQuery.matches ? light : dark
}
storePreference(next)
applyTheme(next)
})
}
})()
Integración con WordPress
Las mejores prácticas recomiendan encolar scripts y estilos correctamente desde functions.php y no imprimir JavaScript inline salvo que sea muy pequeño y crítico.
1) Encolar CSS y JS desde functions.php
Ejemplo de cómo registrar y encolar tus assets ajusta rutas y dependencias según tu tema.
get(Version)) // Encolar script para el toggle depende de que el DOM tenga #theme-toggle wp_enqueue_script(tema-theme-toggle, get_template_directory_uri() . /assets/js/theme-toggle.js, array(), 1.0, true) // Si necesitas pasar una opción PHP a JS: // wp_localize_script(tema-theme-toggle, temaThemeData, array(storageKey => theme-preference)) } add_action(wp_enqueue_scripts, tema_enqueue_color_scheme_assets) ?>
2) Insertar el botón en el menú automáticamente (filtrar wp_nav_menu_items)
Si quieres que el botón aparezca en el menú principal sin editar header.php:
theme_location === primary) {
toggle =
items .= 3) Marcar la preferencia también server-side (opcional)
Si necesitas que el HTML inicial ya incluya el atributo data-theme según una preferencia guardada en una cookie o meta de usuario, puedes imprimirlo en header.php. Con localStorage puro el initial paint puede tener un cambio visual momentáneo si el CSS no aplica rápido el data-theme para evitarlo, hay técnicas de script inline crítico que leen localStorage y escriben el atributo antes de CSS cargar, pero eso debe usarse con cuidado y preferiblemente inline en el head para evitar flashes.
/ Inline (opcional y crítico): antes de cargar CSS, puedes poner esto en head para evitar FOUC.
Atención: al inyectarlo en WP, usa wp_add_inline_script o imprimir en head con acción adecuada.
/
(function () {
try {
var pref = localStorage.getItem(theme-preference)
if (pref === dark pref === light) {
document.documentElement.setAttribute(data-theme, pref)
}
} catch (e) {}
})()
Accesibilidad y UX
- El botón debe ser foco-navegable y tener aria-pressed actualizado para informar el estado.
- Proporcionar texto visible o visually-hidden para lectores de pantalla.
- Usar contraste suficiente en ambos temas para garantizar legibilidad (según WCAG).
- Considerar animaciones sutiles y respetar la preferencia prefers-reduced-motion.
Compatibilidad y consideraciones
| Funcionalidad | Soporte | Notas |
|---|---|---|
| prefers-color-scheme | Chrome, Edge, Firefox, Safari modernos | Versiones antiguas no la reconocen: el fallback será el :root por defecto |
| CSS variables | Browsers modernos | IE11 no soporta variables. Para soporte IE se requieren estilos fallback sin custom properties o un polyfill |
| localStorage | Todos los navegadores modernos | En contextos de bloqueo de almacenamiento (e.g. cookies deshabilitadas) el código debe manejar excepciones |
Buenas prácticas y recomendaciones finales
- Define todas las variables que usarás en componentes (colores, sombras, bordes) para facilitar cambios de diseño.
- Coloca la regla @media (prefers-color-scheme: dark) antes de las reglas [data-theme=…] para que el override manual tenga prioridad por orden de cascada.
- Evita ocultar información visual importante solo con color combina color con íconos o cambios de contraste.
- Prueba en varios navegadores y con el switch del sistema (macOS, iOS, Windows) y en navegadores móviles.
- Si quieres que el servidor renderice ya con tema (por ejemplo para mejorar CLS/FOUC), considera técnicas server-side o inline script muy pequeño que lea la preferencia guardada en localStorage y añada el atributo al html antes de que se cargue el CSS principal.
Ejemplos rápidos de casos prácticos
1) Forzar tema oscuro temporalmente desde la consola (prueba rápida)
document.documentElement.setAttribute(data-theme, dark)
2) Eliminar override y volver a preferencia del sistema
document.documentElement.removeAttribute(data-theme)
Conclusión
Combinar prefers-color-scheme con variables CSS y un override basado en atributo localStorage proporciona una solución robusta y flexible para temas claro/oscuro en WordPress. La integración correcta en functions.php y un toggle accesible aseguran buena experiencia de usuario y mantenimiento sencillo. Implementando las recomendaciones de este tutorial tendrás un control total sobre la paleta, comportamiento por defecto y la posibilidad de que el usuario personalice su experiencia sin perder compatibilidad con la preferencia del sistema.
Leave a Reply