Introducción
Este tutorial explica paso a paso cómo crear un layout de comparativa de temas WordPress utilizando CSS Grid. El objetivo es obtener una tabla de comparación moderna, accesible y totalmente responsive que pueda integrarse en cualquier tema de WordPress. Se incluyen ejemplos de código PHP para la plantilla, HTML de ejemplo, CSS Grid avanzado, JavaScript para interactividad (toggle de precios, filtros) y la forma correcta de registrar y cargar los assets en functions.php.
Requisitos y consideraciones
- WordPress (cualquier versión moderna que soporte WP_Query y funciones de enqueue).
- Un tema hijo o personalizado donde añadir plantillas y estilos.
- Conocimientos básicos de PHP, CSS y JavaScript.
- Acceso para añadir una página o plantilla que muestre la comparativa.
Concepto del layout
La estructura típica de una comparativa es una fila superior con el nombre y precio del tema y filas verticales con las características (compatibilidad, soporte, actualizaciones, iconos, calificación). Con CSS Grid podemos diseñar una cuadrícula flexible en la que:
- La primera columna actúa como columna de labels (nombres de características).
- Las columnas siguientes corresponden a cada tema comparado.
- El encabezado de las columnas puede quedar fijo (sticky) para facilitar la navegación en filas largas.
Estructura de datos en WordPress
Lo ideal es tener un Custom Post Type (por ejemplo theme_item) o posts con meta datos que describan cada tema (precio, enlace, características booleanas, iconos, rating). A modo de ejemplo se muestra una consulta simple con WP_Query para recuperar los elementos a comparar.
Plantilla PHP: obtener los items
lt?php
// Plantilla: template-compare-themes.php (ejemplo)
// Recupera posts tipo theme_item o posts normales con categoría themes
args = array(
post_type =gt theme_item,
post_status =gt publish,
posts_per_page =gt -1,
orderby =gt menu_order,
order =gt ASC,
)
themes = new WP_Query(args)
items = array()
if (themes-gthave_posts()) {
while (themes-gthave_posts()) {
themes-gtthe_post()
id = get_the_ID()
items[] = array(
title =gt get_the_title(),
permalink =gt get_permalink(),
thumbnail =gt get_the_post_thumbnail_url(id, thumbnail),
price_month =gt get_post_meta(id, price_month, true),
price_year =gt get_post_meta(id, price_year, true),
features =gt array(
responsive =gt get_post_meta(id, feat_responsive, true),
support =gt get_post_meta(id, feat_support, true),
updates =gt get_post_meta(id, feat_updates, true),
// añadir más según necesidades
),
rating =gt floatval(get_post_meta(id, rating, true)),
)
}
wp_reset_postdata()
}
// Pasar items a la vista (imprimir HTML más abajo)
?gt
Markup HTML recomendado
El markup que se mostrará en el frontend puede generarse dinámicamente con PHP. Este ejemplo simplificado genera una estructura semántica donde la primera columna contiene las etiquetas de las filas y las siguientes columnas los temas.
lt!-- Contenedor principal de la comparativa --gt
ltdiv class=compare-gridgt
ltdiv class=compare-head label-cellgtlt/divgt lt!-- celda vacía en la esquina superior izquierda --gt
ltdiv class=compare-head theme-cellgt
lth3gtTema Alt/h3gt
ltp class=price data-month=19 data-year=99gtltspan class=amountgt19lt/spangt/meslt/pgt
lta class=btn href=#gtVer demolt/agt
lt/divgt
ltdiv class=compare-head theme-cellgt
lth3gtTema Blt/h3gt
ltp class=price data-month=29 data-year=199gtltspan class=amountgt29lt/spangt/meslt/pgt
lta class=btn href=#gtVer demolt/agt
lt/divgt
lt!-- Filas de características: primera columna labels, siguientes columnas valores --gt
ltdiv class=label-cellgtResponsivelt/divgt
ltdiv class=value-cellgtSílt/divgt
ltdiv class=value-cellgtSílt/divgt
ltdiv class=label-cellgtSoportelt/divgt
ltdiv class=value-cellgt6 meseslt/divgt
ltdiv class=value-cellgt12 meseslt/divgt
lt!-- ... seguir con más filas --gt
lt/divgt
CSS Grid: base del layout
Usaremos CSS Grid para definir columnas: la primera columna tendrá ancho fijo o mínimo y las columnas de temas serán fluidas. Se agrega un comportamiento responsive para pantallas pequeñas donde se permite scroll horizontal o se transforma en layout por tarjeta.
/ Grid base /
.compare-grid {
display: grid
grid-template-columns: 220px repeat(auto-fit, minmax(220px, 1fr))
align-items: start
gap: 1rem
overflow-x: auto / permite desplazamiento horizontal en pantallas pequeñas /
padding: 1rem
}
/ Encabezados y celdas /
.compare-head {
position: sticky
top: 0
background: #fff
z-index: 3
border-bottom: 2px solid #eee
padding: 1rem
}
.label-cell {
background: #fafafa
padding: 0.75rem 1rem
border-bottom: 1px solid #eee
align-self: center
font-weight: 600
}
.value-cell {
padding: 0.75rem 1rem
border-bottom: 1px solid #eee
text-align: center
}
/ Asegurar que la primera columna se mantenga fija visualmente /
.compare-grid .label-cell:first-of-type {
position: sticky
left: 0
z-index: 2
background: #fafafa
}
/ Ajustes responsive: en móviles se puede mostrar por tarjetas /
@media (max-width: 700px) {
.compare-grid {
grid-template-columns: 1fr / mostrar en pila /
}
.compare-head {
position: relative / ya no sticky en móvil si se apila /
}
.label-cell, .value-cell {
display: block
width: 100%
}
}
Explicación de decisiones CSS
- grid-template-columns: la primera columna fija (220px) para etiquetas, las demás son repetidas y responsivas.
- position: sticky en encabezados y en la primera columna mejora la usabilidad al desplazarse.
- overflow-x: auto permite scroll horizontal si hay demasiadas columnas en pantallas medias.
- Media queries para convertir la tabla en pila (card stack) en móviles pequeños.
Interactividad: toggle precios mensual/anual y filtros
Un pequeño script cambia la visualización de precios entre mensual y anual. También se pueden añadir filtros por compatibilidad o calificación.
// toggle-precios.js (ejemplo sencillo)
document.addEventListener(DOMContentLoaded, function () {
const toggle = document.querySelector(#toggle-pricing) // botón o checkbox
if (!toggle) returnfunction updatePrices(mode) {
document.querySelectorAll(.price).forEach(function (el) {
const month = el.getAttribute(data-month)
const year = el.getAttribute(data-year)
if (mode === year) {
el.querySelector(.amount).textContent =
Leave a Reply