Tutorial WordPress: Aplicar un esquema de colores claro/oscuro con prefers-color-scheme

·

·

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

Por qué usar variables CSS y prefers-color-scheme

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:

  1. Al cargar la página, leer si hay una preferencia guardada en localStorage.
  2. Si existe, aplicar data-theme=dark o light en document.documentElement.
  3. Si no existe, respetar la preferencia del sistema (por defecto la media query ya la aplica, pero podemos sincronizar el botón con ella).
  4. Escuchar cambios en la media query para actualizar el estado del botón si el usuario no ha forzado preferencia manual.
  5. 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 .= 
  }
  return items
}
add_filter(wp_nav_menu_items, tema_add_theme_toggle_to_menu, 10, 2)
?>

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

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

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

Your email address will not be published. Required fields are marked *