Aprende a estilizar filas alternantes de ACF Repeater con lógica modulo en PHP o CSS :nth-child(), con ejemplos practicos para temas WordPress.
ES

Campos ACF Repeater - Como estilizar filas alternantes

5.00 /5 - (24 votes )
Última verificación: 1 de mayo de 2026
11min de lectura
Caso de estudio
Desarrollador full-stack

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 filas
  • the_row(): Se mueve a la siguiente fila (como the_post())
  • get_sub_field(): Obtiene el valor del subcampo en la fila actual
  • get_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.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

¿Quieres implementar esto en tu sitio?

Si quieres transformar el artículo en mejoras concretas, rediseño o un plan de implementación, puedo cerrar el alcance y ejecutar.

Cluster relacionado

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

FAQ del artículo

Preguntas Frecuentes

Respuestas prácticas para aplicar el tema en la ejecución real.

SEO-ready GEO-ready AEO-ready 3 Q&A
Cual es la forma más fácil de alternar estilos en filas ACF Repeater?
La opción más sencilla suele ser CSS :nth-child() si solo necesitas cambios visuales. Usa PHP con un contador o comprobacion modulo cuando la lógica del marcado o la plantilla también necesite cambiar.
Como se comprueba si una fila de ACF Repeater es par o impar?
Incrementa un contador dentro del bucle del repeater y usa $i % 2 === 0 para filas pares e $i % 2 !== 0 para filas impares.
Debo usar PHP o CSS para el efecto cebra en ACF Repeater?
Usa CSS cuando la diferencia sea puramente presentacional. Usa PHP cuando necesites diferentes clases, plantillas o salida condicional por fila del repeater.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparamos una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados

Deja de escribir sentencias if desordenadas. Aprende la diferencia entre in_category y has_term, como manejar categorías hijas recursivas eficientemente y optimizar tus etiquetas condicionales.
development

Lógica condicional de WordPress para taxonomías

Deja de escribir sentencias if desordenadas. Aprende la diferencia entre in_category y has_term, como manejar categorías hijas recursivas eficientemente y optimizar tus etiquetas condicionales.

Notas prácticas sobre WP_Query: cuándo get_posts gana a new WP_Query, por qué meta_query sobre claves no indexadas colapsa a escala, y cómo paginar loops personalizados sin caer en 404.
development

Una guía práctica de WP_Query y el Loop (edición de rendimiento 2026)

Notas prácticas sobre WP_Query: cuándo get_posts gana a new WP_Query, por qué meta_query sobre claves no indexadas colapsa a escala, y cómo paginar loops personalizados sin caer en 404.

has_term() e is_tax() se confunden a menudo. Consulta la guía completa sobre lógica condicional para categorías, etiquetas y taxonomías personalizadas.
development

Comprobar si una entrada pertenece a un termino de taxonomía

has_term() e is_tax() se confunden a menudo. Consulta la guía completa sobre lógica condicional para categorías, etiquetas y taxonomías personalizadas.