El campo Repeater en Advanced Custom Fields (ACF) es una de las funcionalidades más potentes para desarrolladores. Permite a los clientes anadir un número ilimitado de elementos (p. ej., “Socios”, “Agenda”, “Ingredientes”) sin necesidad de entradas separadas.
Descubre más sobre desarrollo profesional WordPress en WPPoland. Pero, como los estilizas? Especialmente si quieres que cada segundo elemento tenga un aspecto diferente.
En la práctica, la solución más rápida es usar un simple contador más lógica modulo cuando necesitas diferentes clases o plantillas, o CSS :nth-child() cuando el cambio es puramente visual.
Si solo necesitas colores o espaciado alternante, empieza con CSS :nth-child(). Si cada fila del repeater necesita diferentes clases, marcado o plantillas condicionales, usa un contador PHP con el operador modulo dentro del bucle ACF.
Entendiendo los campos ACF Repeater
Los campos ACF Repeater revolucionaron la gestión de contenido en WordPress al permitir bloques de contenido flexibles y repetibles. En lugar de crear tipos de entrada personalizados o entradas separadas para contenido similar, puedes crear un único grupo de campos que los clientes pueden llenar dinamicamente.
Casos de uso comunes:
- Miembros del equipo: Lista de perfiles del equipo con fotos y biografias
- Testimonios: Resenas de clientes con nombres, fotos y citas
- Secciones de preguntas frecuentes: Preguntas y respuestas
- Elementos de linea temporal: Eventos históricos o hitos del proyecto
- Caracteristicas del producto: Lista de características con iconos y descripciones
- Tablas de precios: Multiples niveles de precios con diferentes características
Bucle básico de ACF Repeater
Aqui esta la estructura fundamental para mostrar los datos de un campo repeater:
<?php if( have_rows('my_repeater') ): ?>
<ul class="slides">
<?php while( have_rows('my_repeater') ): the_row();
$image = get_sub_field('image');
$content = get_sub_field('text');
$title = get_sub_field('title');
?>
<li class="slide">
<?php if( $image ): ?>
<img src="<?php echo esc_url( $image['url'] ); ?>"
alt="<?php echo esc_attr( $image['alt'] ); ?>" />
<?php endif; ?>
<?php if( $title ): ?>
<h3><?php echo esc_html( $title ); ?></h3>
<?php endif; ?>
<?php if( $content ): ?>
<p><?php echo wp_kses_post( $content ); ?></p>
<?php endif; ?>
</li>
<?php endwhile; ?>
</ul>
<?php else: ?>
<p>No se encontraron elementos.</p>
<?php endif; ?>
Funciones clave:
have_rows(): Comprueba si el repeater tiene filasthe_row(): Se mueve a la siguiente fila (comothe_post())get_sub_field(): Obtiene el valor del subcampo en la fila actualget_row_index(): Devuelve el número de fila actual (base 0)
Desafio: El patron “cebra”
Los disenadores a menudo quieren que cada fila alternante tenga un fondo oscuro o un estilo diferente. Esto crea separacion visual y mejora la legibilidad.
Solución PHP: Usando el operador modulo
En PHP, usamos una variable contador ($i) y el operador modulo (%) para determinar si un número es par o impar.
<?php if( have_rows('sections') ):
$i = 0; // Inicializar contador
?>
<div class="sections-container">
<?php while( have_rows('sections') ): the_row();
$i++; // Incrementar contador
// Operador modulo: devuelve el resto de la division
// $i % 2 == 0 significa número par (2do, 4to, 6to...)
// $i % 2 == 1 significa número impar (1ro, 3ro, 5to...)
$is_even = ( $i % 2 == 0 );
$class = $is_even ? 'bg-dark' : 'bg-light';
// Alternativa: Mas legible
$class = ( $i % 2 === 0 ) ? 'even-row' : 'odd-row';
?>
<div class="row <?php echo esc_attr( $class ); ?>">
<div class="row-content">
<?php
$title = get_sub_field('title');
$content = get_sub_field('content');
?>
<?php if( $title ): ?>
<h3><?php echo esc_html( $title ); ?></h3>
<?php endif; ?>
<?php if( $content ): ?>
<div class="content">
<?php echo wp_kses_post( $content ); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>
Entendiendo el operador modulo
El operador modulo (%) devuelve el resto de una division:
1 % 2 = 1 // 1 dividido entre 2 = 0 resto 1 (impar)
2 % 2 = 0 // 2 dividido entre 2 = 1 resto 0 (par)
3 % 2 = 1 // 3 dividido entre 2 = 1 resto 1 (impar)
4 % 2 = 0 // 4 dividido entre 2 = 2 resto 0 (par)
Patron:
- Números pares:
% 2 === 0 - Números impares:
% 2 === 1(o!== 0)
Patrones de estilizacion avanzados
Patron 1: Cada tercer elemento diferente
<?php if( have_rows('items') ):
$i = 0;
?>
<div class="items-grid">
<?php while( have_rows('items') ): the_row();
$i++;
$modulo = $i % 3;
if ( $modulo === 0 ) {
$class = 'highlight-item'; // 3ro, 6to, 9no...
} elseif ( $modulo === 1 ) {
$class = 'normal-item'; // 1ro, 4to, 7mo...
} else {
$class = 'secondary-item'; // 2do, 5to, 8vo...
}
?>
<div class="item <?php echo esc_attr( $class ); ?>">
<!-- Contenido -->
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>
Patron 2: Primer y último elemento especiales
<?php if( have_rows('items') ):
$i = 0;
$total = count( get_field('items') );
?>
<div class="items-list">
<?php while( have_rows('items') ): the_row();
$i++;
$classes = array();
if ( $i === 1 ) {
$classes[] = 'first-item';
}
if ( $i === $total ) {
$classes[] = 'last-item';
}
if ( $i % 2 === 0 ) {
$classes[] = 'even-item';
} else {
$classes[] = 'odd-item';
}
$class_string = implode( ' ', $classes );
?>
<div class="item <?php echo esc_attr( $class_string ); ?>">
<!-- Contenido -->
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>
Patron 3: Indice de fila para lógica compleja
<?php if( have_rows('sections') ): ?>
<div class="sections">
<?php while( have_rows('sections') ): the_row();
$row_index = get_row_index(); // Indice base 1
$row_number = $row_index - 1; // Base 0 para calculos
// Plantilla diferente para los primeros 3 elementos
if ( $row_index <= 3 ) {
get_template_part( 'template-parts/section', 'featured' );
} else {
get_template_part( 'template-parts/section', 'standard' );
}
?>
<?php endwhile; ?>
</div>
<?php endif; ?>
CSS nth-child: La forma moderna
En 2026, si el cambio de estilo es puramente visual (colores, espaciado, bordes), CSS :nth-child() es a menudo más limpio y eficiente que la lógica PHP.
Patrones básicos de nth-child
/* Cada fila par (2da, 4ta, 6ta...) */
.row:nth-child(even) {
background-color: #f5f5f5;
}
/* Cada fila impar (1ra, 3ra, 5ta...) */
.row:nth-child(odd) {
}
/* Cada 3er elemento */
.item:nth-child(3n) {
border-left: 3px solid #0073aa;
}
/* Primeros 3 elementos */
.item:nth-child(-n+3) {
font-weight: bold;
}
/* Cada elemento despues del 5to */
.item:nth-child(n+6) {
opacity: 0.8;
}
Patrones CSS avanzados
/* Efecto cebra */
.sections-container .row:nth-child(even) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.sections-container .row:nth-child(odd) {
background: #fff;
color: #333;
}
/* Efectos hover en filas alternantes */
.row:nth-child(even):hover {
transform: translateX(10px);
}
/* Diferentes disposiciones */
.row:nth-child(3n+1) {
grid-column: 1 / 3; /* Abarca 2 columnas */
}
Cuando usar PHP vs CSS
Usa PHP cuando:
- Estructura HTML diferente para filas pares/impares
- Carga de diferentes template parts
- Lógica condicional más alla del estilo (p. ej., diferentes campos)
- Nombres de clase dinámicos basados en valores de campos
- Calculos complejos (p. ej., “cada 5to elemento despues del 10mo”)
Usa CSS cuando:
- Estilo puramente visual (colores, espaciado, bordes)
- Patrones simples (cada 2do, 3ro, etc.)
- El rendimiento importa (CSS es más rápido que bucles PHP)
- Diseño responsivo (media queries CSS)
Ejemplo completo: Cuadricula de miembros del equipo
Aqui hay un ejemplo completo listo para producción:
<?php if( have_rows('team_members') ): ?>
<div class="team-grid">
<?php
$i = 0;
while( have_rows('team_members') ): the_row();
$i++;
$name = get_sub_field('name');
$role = get_sub_field('role');
$photo = get_sub_field('photo');
$bio = get_sub_field('bio');
$email = get_sub_field('email');
// Determinar disposicion: miembros destacados (primeros 3) obtienen tarjetas más grandes
$is_featured = ( $i <= 3 );
$card_class = $is_featured ? 'team-card featured' : 'team-card';
$card_class .= ( $i % 2 === 0 ) ? ' even' : ' odd';
?>
<article class="<?php echo esc_attr( $card_class ); ?>" data-index="<?php echo $i; ?>">
<?php if( $photo ): ?>
<div class="team-photo">
<img src="<?php echo esc_url( $photo['sizes']['medium'] ); ?>"
alt="<?php echo esc_attr( $name ); ?>"
srcset="<?php echo esc_url( $photo['sizes']['medium'] ); ?> 300w,
<?php echo esc_url( $photo['sizes']['large'] ); ?> 600w"
sizes="(max-width: 600px) 300px, 600px" />
</div>
<?php endif; ?>
<div class="team-info">
<?php if( $name ): ?>
<h3 class="team-name"><?php echo esc_html( $name ); ?></h3>
<?php endif; ?>
<?php if( $role ): ?>
<p class="team-role"><?php echo esc_html( $role ); ?></p>
<?php endif; ?>
<?php if( $bio ): ?>
<div class="team-bio">
<?php echo wp_kses_post( $bio ); ?>
</div>
<?php endif; ?>
<?php if( $email ): ?>
<a href="mailto:<?php echo esc_attr( $email ); ?>" class="team-email">
Contacto
</a>
<?php endif; ?>
</div>
</article>
<?php endwhile; ?>
</div>
<?php else: ?>
<p class="no-items">No se encontraron miembros del equipo.</p>
<?php endif; ?>
CSS acompanante:
.team-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
.team-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.team-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
/* Tarjetas destacadas (primeras 3) */
.team-card.featured {
grid-column: span 2;
}
/* Efecto cebra */
.team-card.even {
background: #f8f9fa;
}
/* Responsivo: Tarjetas destacadas se vuelven normales en móvil */
@media (max-width: 768px) {
.team-card.featured {
grid-column: span 1;
}
}
Optimización del rendimiento
Almacenar en cache los datos del repeater
Para campos repeater que no cambian a menudo, almacena en cache la salida:
function get_cached_repeater_output( $field_name ) {
$cache_key = 'repeater_' . $field_name . '_' . get_the_ID();
$output = get_transient( $cache_key );
if ( false === $output ) {
ob_start();
// Tu bucle repeater aqui
$output = ob_get_clean();
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
}
return $output;
}
Limitar elementos del repeater
Si tienes muchos elementos, considera la páginación:
<?php
$items = get_field('items');
$items_per_page = 12;
$current_page = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
$offset = ( $current_page - 1 ) * $items_per_page;
$páginated_items = array_slice( $items, $offset, $items_per_page );
foreach ( $páginated_items as $item ):
// Mostrar elemento
endforeach;
?>
Mejores prácticas
1. Siempre escapar la salida
// Correcto
echo esc_html( $title );
echo esc_url( $image['url'] );
echo wp_kses_post( $content );
// Incorrecto
echo $title; // Vulnerabilidad XSS
2. Comprobar si los campos existen
if ( $image && ! empty( $image['url'] ) ) {
// Usar imagen
}
3. Usar get_row_index() para depuracion
$row_index = get_row_index();
error_log( "Procesando fila $row_index" );
4. Combinar PHP y CSS
Usa PHP para la lógica, CSS para el estilo:
// PHP: Anadir atributo data
<div class="item" data-index="<?php echo $i; ?>">
/* CSS: Estilizar basado en atributo data si es necesario */
.item[data-index="1"] {
/* Estilo especial */
}
Resolución de problemas
Problema: El contador no funciona
Solución: Asegurate de que el contador se inicializa antes del bucle:
$i = 0; // Debe estar antes de while()
while( have_rows() ):
$i++;
Problema: CSS nth-child no funciona
Solución: Comprueba si hay elementos envolventes que afectan a nth-child:
/* Si los elementos estan envueltos, apunta a los hijos del contenedor */
.container > .item:nth-child(even) { }
Problema: Modulo devuelve valores incorrectos
Solución: Usa comparación estricta:
// Correcto
if ( $i % 2 === 0 ) { }
// Evitar (comparacion flexible)
if ( $i % 2 == 0 ) { }
Resumen
Estilizar los campos ACF Repeater requiere comprender tanto la lógica PHP como los selectores CSS. El operador modulo (%) es tu aliado para crear patrones alternantes en PHP, mientras que CSS :nth-child() es perfecto para estilos puramente visuales.
Puntos clave:
- Usa el operador modulo para patrones alternantes basados en PHP
- Prefiere CSS
:nth-child()para cambios solo visuales - Combina ambos enfoques para disposiciones complejas
- Siempre escapa la salida por seguridad
- Almacena en cache la salida del repeater para rendimiento
- Usa
get_row_index()para depuracion y lógica compleja
En 2026, con las capacidades CSS modernas, prefiere soluciones CSS cuando sea posible, pero no dudes en usar PHP cuando necesites diferentes estructuras HTML o lógica condicional compleja.
