Introducción
En este artículo encontrarás un tutorial completo para personalizar el paginador numérico de WordPress usando CSS y gestionando correctamente los estados activos, hover, foco y deshabilitado. Verás desde cómo generar la estructura HTML desde PHP en tu tema hasta ejemplos de CSS moderno (con variables, transiciones y accesibilidad). Todo listo para aplicar en tu tema hijo o plugin.
Qué vamos a conseguir
- Una estructura de paginación semántica y accesible (nav ul/li).
- Estilos visuales modernos para elementos normales, hover, foco y estado activo.
- Manejo de puntos suspensivos (…) y botones de anterior/siguiente.
- Recomendaciones para integración con el theme, responsive y rendimiento.
1. Generar la estructura de paginación desde PHP
WordPress ofrece funciones como paginate_links() o get_the_posts_pagination(). paginate_links() con type => array es muy cómoda porque devuelve cada enlace por separado, incluyendo el elemento current (actual) y los puntos suspensivos. A continuación tienes un ejemplo completo que convierte la salida en una lista ul/li con clases manejables desde CSS.
lt?php
// Coloca este código en el archivo de tu loop (archive.php, index.php) o en una plantilla parcial.
// Asegúrate de usarlo donde haya un query paginado (main loop).
links = paginate_links( array(
base =gt str_replace( 999999999, %#%, esc_url( get_pagenum_link( 999999999 ) ) ),
format =gt ?paged=%#%,
current =gt max( 1, get_query_var( paged ) ),
total =gt wp_query-gtmax_num_pages,
prev_text =gt laquo,
next_text =gt raquo,
type =gt array,
mid_size =gt 1,
) )
if ( is_array( links ) ) :
echo ltnav class=custom-pagination role=navigation aria-label=Paginacióngt
echo ltul class=paginationgt
foreach ( links as link ) {
// Paginate_links devuelve:
// - anchor lta class=page-numbers href=...gtNlt/agt
// - span ltspan class=page-numbers currentgtNlt/spangt
// - span ltspan class=page-numbers dotsgt...lt/spangt
if ( strpos( link, current ) !== false ) {
// Página actual: convierte el span en un elemento no clickable con clase page-link current
echo ltli class=page-itemgt . str_replace( page-numbers current, page-link current, link ) . lt/ligt
} elseif ( strpos( link, dots ) !== false ) {
// Puntos suspensivos: mantén un span con clase page-link dots y aria-hidden
dots = str_replace( page-numbers dots, page-link dots, link )
// Añadir aria-hidden para que no sea leído como control
dots = str_replace( ltspan, ltspan aria-hidden=true, dots )
echo ltli class=page-item dotsgt . dots . lt/ligt
} else {
// Enlaces normales: añade clase page-link a los anchors
echo ltli class=page-itemgt . str_replace( page-numbers, page-link, link ) . lt/ligt
}
}
echo lt/ulgt
echo lt/navgt
endif
?gt
Con este código obtendrás HTML coherente como:
- ltnav class=custom-pagination role=navigationgt
- ltul class=paginationgt
- ltli class=page-itemgtlta class=page-link href=…gt1lt/agtlt/ligt …
- ltli class=page-itemgtltspan class=page-link currentgt2lt/spangtlt/ligt
- ltli class=page-item dotsgtltspan class=page-link dots aria-hidden=truegt…lt/spangtlt/ligt
2. Estilos CSS detallados
A continuación tienes una hoja de estilos CSS completa y comentada. Usa variables CSS para integrar fácilmente con el resto del tema y modificar colores/espaciados. Añádela a tu style.css del tema hijo o a un archivo CSS encolado por functions.php.
/ Variables globales para fácil personalización /
:root {
--pag-bg: transparent
--pag-border: #e0e0e0
--pag-radius: 6px
--pag-color: #333
--pag-accent: #0073aa / color principal (activo) /
--pag-accent-contrast: #ffffff
--pag-hover: #f5faff
--pag-disabled: #cfcfcf
--pag-gap: 8px
--pag-font-size: 0.95rem
--pag-padding: 8px 12px
}
/ Contenedor /
.custom-pagination {
margin: 28px 0
text-align: center
}
/ Lista horizontal /
.custom-pagination .pagination {
display: inline-flex
flex-wrap: wrap
list-style: none
padding: 0
margin: 0
gap: var(--pag-gap)
align-items: center
}
/ Elemento de lista (no afecta estado) /
.custom-pagination .page-item {
display: inline-flex
}
/ Enlaces y spans: botón base /
.custom-pagination .page-link {
display: inline-flex
align-items: center
justify-content: center
min-width: 40px
padding: var(--pag-padding)
font-size: var(--pag-font-size)
color: var(--pag-color)
background: var(--pag-bg)
border: 1px solid var(--pag-border)
border-radius: var(--pag-radius)
text-decoration: none
transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease
cursor: pointer
}
/ Hover y focus: mejora la interacción /
.custom-pagination .page-link:hover,
.custom-pagination .page-link:focus {
background: var(--pag-hover)
transform: translateY(-2px)
box-shadow: 0 4px 12px rgba(16,24,40,0.06)
}
/ Focus visible (mejor accesibilidad) /
.custom-pagination .page-link:focus-visible {
outline: 3px solid rgba(0,115,170,0.18)
outline-offset: 2px
}
/ Estado activo (página actual) /
.custom-pagination .page-link.current {
background: var(--pag-accent)
color: var(--pag-accent-contrast)
border-color: transparent
transform: none
cursor: default
box-shadow: 0 6px 18px rgba(0,115,170,0.12)
font-weight: 600
}
/ Prev/Next estilizados si son links /
.custom-pagination .page-item:first-child .page-link,
.custom-pagination .page-item:last-child .page-link {
padding-left: 10px
padding-right: 10px
min-width: auto
}
/ Puntos suspensivos: estilo neutro y sin interacción /
.custom-pagination .page-item.dots .page-link {
cursor: default
background: transparent
border: none
color: var(--pag-disabled)
transform: none
box-shadow: none
font-weight: 500
}
/ Estado disabled si lo necesitas (por ejemplo prev/next en extremos) /
.custom-pagination .page-link[aria-disabled=true],
.custom-pagination .page-link.disabled {
opacity: 0.6
pointer-events: none
color: var(--pag-disabled)
background: transparent
border-color: var(--pag-border)
}
/ Responsive: botones más pequeños en pantallas estrechas /
@media (max-width: 480px) {
.custom-pagination .page-link {
padding: 6px 8px
min-width: 34px
font-size: 0.88rem
}
.custom-pagination .pagination {
gap: 6px
}
}
Notas sobre el CSS
- Usa variables para adaptarlo al tema: cambia –pag-accent para que coincida con la paleta.
- El uso de :focus-visible garantiza un buen comportamiento de teclado sin añadir outline a clics de ratón.
- Las transiciones dan una sensación de calidad mantenlas cortas para no afectar la percepción de rendimiento.
3. Mejora de accesibilidad y ARIA
La estructura nav ul/li con un label aria-label ya proporciona contexto. Algunas prácticas recomendadas adicionales:
- Añade role=navigation y aria-label al nav (ya incluido en el PHP de ejemplo).
- Evita hacer interactivos los puntos suspensivos. Usa aria-hidden=true para los elementos que no son controles.
- Para enlaces prev/next, añade atributo aria-disabled=true cuando no haya página anterior/siguiente.
- Si conviertes el span current en un elemento interactivo, evita que sea clicable y añade aria-current=page. Pero en el ejemplo lo dejamos como span sin enlace, puedes añadir aria-current si lo prefieres:
// Ejemplo: añadir aria-current al span current (si quieres)
/ ... dentro del loop que procesa links ... /
if ( strpos( link, current ) !== false ) {
link = str_replace( page-numbers current, page-link current, link )
// Añadir aria-current si el elemento es un span:
link = str_replace( ltspan, ltspan aria-current=page, link )
echo ltli class=page-itemgt . link . lt/ligt
}
4. Manejo de prev/next deshabilitado
paginate_links puede devolver prev/next incluso cuando no hay página anterior o siguiente. En ese caso, detecta la ausencia y marca el botón como deshabilitado para que el lector de pantalla lo entienda y el CSS lo oculte de la interacción:
// Ejemplo rápido para marcar prev/next como deshabilitados
global wp_query
current = max( 1, get_query_var( paged ) )
total = wp_query-gtmax_num_pages
prev_disabled = ( current lt= 1 )
next_disabled = ( current gt= total )
// Al imprimir botones prev/next manualmente:
if ( prev_disabled ) {
echo ltli class=page-itemgtltspan class=page-link aria-disabled=truegtlaquolt/spangtlt/ligt
} else {
echo ltli class=page-itemgtlta class=page-link href= . get_pagenum_link( current - 1 ) . gtlaquolt/agtlt/ligt
}
5. Integración con get_the_posts_pagination()
Si prefieres usar get_the_posts_pagination(), puedes usar su parámetro type => list que ya devuelve una lista ul. Después solo aplica CSS adaptando selectores. Ejemplo rápido:
echo get_the_posts_pagination( array(
mid_size =gt 1,
prev_text =gt laquo,
next_text =gt raquo,
screen_reader_text =gt ,
type =gt list,
) )
Nota: la salida de get_the_posts_pagination puede incluir clases distintas inspecciónala y ajusta el CSS o usa selectores más concretos (p.ej. .custom-pagination .pagination a).
6. Variantes estéticas y micro-interacciones
Algunas ideas para personalización avanzada:
- Usar iconos SVG para prev/next en lugar de laquo y raquo.
- Aplicar animaciones suaves al cambiar de página (prefetch o skeletons) para mejorar UX.
- Añadir un pequeño tooltip con el número de página al pasar el cursor (title en anchors o data-tooltip).
- Utilizar CSS grid para distribuir elementos si quieres que prev y next estén en extremos y números centrados.
7. Rendimiento y caché
La paginación en sí no suele causar problemas de rendimiento, pero si tu loop es pesado:
- Utiliza transients o plugin de caché para páginas de archivo que no cambian con frecuencia.
- Evita consultas adicionales dentro del loop para construir la paginación. Usa el global wp_query y paginate_links como en el ejemplo.
8. Pruebas y comprobaciones
Antes de publicar en producción:
- Prueba con distintos tamaños de contenido y páginas (1, 2, muchas páginas) para comprobar el comportamiento de dots y prev/next.
- Verifica con teclado (tab) que el foco llega a enlaces y que :focus-visible se aplica.
- Prueba en móviles para asegurarte que el tamaño mínimo de toque es cómodo (al menos 40px recomendado).
- Comprueba con lector de pantalla que la navegación se anuncia correctamente (nav con aria-label y aria-current en página actual).
9. Resumen rápido de pasos a seguir
- Decide si usas paginate_links() o get_the_posts_pagination().
- Genera la estructura semántica nav gt ul gt li con clases coherentes.
- Añade CSS moderno (variables, transiciones, focus-visible, responsive).
- Maneja puntos suspensivos y estados disabled para prev/next.
- Comprueba accesibilidad, pruebas en dispositivos y performance.
Ejemplo final: integración mínima
Implementa el PHP del apartado 1 y pega el CSS en style.css. Cambia las variables :root para adecuarlas al diseño. Con eso tendrás un paginador numérico estilizado, accesible y con estados activos bien definidos.
Fin del tutorial — listo para aplicar
Aplica estos fragmentos directamente en tu tema hijo: el PHP en la plantilla correspondiente y el CSS en style.css o en un archivo encolado. El resultado será un paginador numérico profesional, con estados activos, hover y focus adecuados para una buena experiencia y accesibilidad.
Leave a Reply