Advanced Custom Fields (ACF) - Campos repetíveis e estilização avançada

Advanced Custom Fields (ACF) - Campos repetíveis e estilização avançada

Última verificação: 22 de setembro de 2026
8 min de leitura
Caso de estudo
Desenvolvedor full-stack

O campo do tipo Repeater (Campo Repetível) do plugin Advanced Custom Fields (ACF Pro) é uma das estruturas de dados mais versáteis no desenvolvimento profissional de websites e plataformas corporativas em WordPress. Ele confere aos editores a capacidade de criar coleções dinâmicas de itens, como grelhas de equipa, grelhas de especificações de produtos, secções de perguntas frequentes ou cronologias institucionais, sem obrigar à criação desnecessária de Custom Post Types isolados.

No entanto, à medida que os requisitos de design evoluem, surge invariavelmente a necessidade de aplicar estilos diferenciados: layouts em ziguezague com imagens alternadas à esquerda e à direita, fundos com tonalidades contrastantes tipo “zebra”, ou destaque visual especial para o primeiro ou último elemento da lista.

Neste artigo técnico, exploramos a fundo o funcionamento dos ciclos de iteração do ACF, a anatomia de armazenamento na base de dados, padrões de código PHP limpos e a divisão equilibrada entre lógica de servidor e seletores CSS modernos.

#Anatomia de armazenamento do repeater na base de dados

Antes de escrever qualquer linha de template, é fundamental entender como o ACF persiste as informações do repetidor na base de dados. O WordPress não possui tabelas relacionais nativas para coleções de subcampos; por isso, o ACF utiliza a tabela wp_postmeta de forma serializada ou desdobrada:

  1. Registo de contagem: Uma linha que define o número total de entradas, com a chave nome_do_repeater e o valor numérico (por exemplo, 5).
  2. Registos de subcampos: Para cada linha e subcampo, uma entrada individual no formato nome_do_repeater_{indice}_{nome_do_subcampo} (onde o índice interno do motor inicia em 0).
  3. Mapeamento de definições: Linhas ocultas com prefixo _ associadas aos identificadores únicos do ACF (field_60a...).

Isto significa que um repetidor com 10 linhas e 4 subcampos gera mais de 40 a 50 registos discretos na tabela wp_postmeta. Compreender esta mecânica alerta-nos para o risco de criar loops desnecessariamente aninhados ou chamadas repetidas que degradam o Time to First Byte (TTFB).

#O Ciclo Fundamental: have_rows() e the_row()

A API canónica do ACF baseia-se num padrão de cursor iterativo. As funções have_rows() e the_row() atuam em perfeita harmonia:

<?php if ( have_rows( 'blocos_destaque' ) ) : ?>
    <div class="grelha-destaques">
        <?php while ( have_rows( 'blocos_destaque' ) ) : the_row(); 
            // Obter valores relativos à linha atual
            $titulo = get_sub_field( 'titulo' );
            $descricao = get_sub_field( 'descricao' );
            $imagem = get_sub_field( 'imagem' );
        ?>
            <article class="cartao-item">
                <?php if ( ! empty( $imagem ) ) : ?>
                    <figure class="cartao-figura">
                        <?php echo wp_get_attachment_image( $imagem['id'], 'medium_large' ); ?>
                    </figure>
                <?php endif; ?>
                <div class="cartao-corpo">
                    <h3><?php echo esc_html( $titulo ); ?></h3>
                    <p><?php echo esc_html( $descricao ); ?></p>
                </div>
            </article>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

Um erro comum em equipas juniores é a utilização inadvertida de get_field( 'titulo' ) dentro do bloco while. No contexto de um repetidor, get_field() tentará procurar um campo de primeiro nível associado ao post global, retornando um valor nulo ou incorreto. Deve utilizar-se invariavelmente get_sub_field().

#Estilização Condicional com PHP: get_row_index() e Operador Módulo

Quando o layout gráfico exige mudanças estruturais profundas no HTML, como a alternância da posição do bloco de texto e da imagem para criar uma composição visual em ziguezague, a lógica condicional em PHP é indispensável.

#Utilizar o contador nativo do ACF

Em vez de inicializar variáveis manuais ($i = 0), o ACF disponibiliza a função nativa get_row_index(). Esta função devolve o índice da linha em execução atual, começando em 1:

<?php if ( have_rows( 'seccoes_timeline' ) ) : ?>
    <section class="timeline-container">
        <?php while ( have_rows( 'seccoes_timeline' ) ) : the_row(); 
            $indice = get_row_index();
            $e_par = ( $indice % 2 === 0 );
            $classe_alinhamento = $e_par ? 'layout-invertido' : 'layout-padrao';
            
            $titulo = get_sub_field( 'etapa_titulo' );
            $detalhes = get_sub_field( 'etapa_detalhes' );
            $icone = get_sub_field( 'etapa_icone' );
        ?>
            <div class="timeline-bloco <?php echo esc_attr( $classe_alinhamento ); ?>" data-ordem="<?php echo esc_attr( (string) $indice ); ?>">
                <div class="bloco-marcador">
                    <span class="marcador-numero"><?php echo esc_html( (string) $indice ); ?></span>
                </div>
                <div class="bloco-conteudo">
                    <h4><?php echo esc_html( $titulo ); ?></h4>
                    <div class="texto-formatado">
                        <?php echo wp_kses_post( $detalhes ); ?>
                    </div>
                </div>
            </div>
        <?php endwhile; ?>
    </section>
<?php endif; ?>

O operador módulo (%) calcula o resto da divisão inteira por 2. Quando o resto é igual a zero, a linha é par, permitindo aplicar facilmente classes CSS dinâmicas como layout-invertido para inverter a direção do Flexbox (flex-direction: row-reverse).

#Identificar a primeira e última iteração

Muitas vezes é necessário dar destaque ao primeiro elemento (por exemplo, um cartão hero expandido) ou aplicar tratamentos especiais ao último item da coleção. Podemos comparar o índice com a contagem total de linhas recorrendo a count() sobre o valor recuperado:

<?php
$linhas = get_field( 'seccoes_timeline' );
$total = is_array( $linhas ) ? count( $linhas ) : 0;

if ( have_rows( 'seccoes_timeline' ) ) :
    while ( have_rows( 'seccoes_timeline' ) ) : the_row();
        $atual = get_row_index();
        
        $classes = [];
        if ( $atual === 1 ) {
            $classes[] = 'primeiro-elemento-destaque';
        }
        if ( $atual === $total ) {
            $classes[] = 'ultimo-elemento-terminal';
        }
?>
    <div class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>">
        <!-- Renderização do elemento -->
    </div>
<?php
    endwhile;
endif;

#Separação de responsabilidades: quando usar CSS moderno

Nem toda a variação visual requer lógica no servidor. Na verdade, introduzir classes PHP condicionais para alterações puramente decorativas (como cores de fundo alternadas, bordas ou alinhamentos de texto simples) cria complexidade desnecessária no backend.

O CSS contemporâneo oferece o pseudo-seletor :nth-child(), que realiza esta tarefa de forma declarativa e com rendimento ótimo no navegador:

/* Alternância de cores em grelha sem sujar o template PHP */
.grelha-destaques .cartao-item:nth-child(even) {
    background-color: #f8fafc;
    border-left: 4px solid #0284c7;
}

.grelha-destaques .cartao-item:nth-child(odd) {
    background-color: #ffffff;
    border-left: 4px solid #64748b;
}

/* Destaque para o primeiro item */
.grelha-destaques .cartao-item:first-child {
    box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}

#Regra prática de decisão técnica

  1. Escolha CSS (:nth-child): Sempre que a diferença entre linhas se limitar a atributos cosméticos, como cor de fundo, tipografia, bordas, sombras ou transições visuais.
  2. Escolha PHP (get_row_index): Quando a alternância exigir a inclusão de tags HTML distintas, carregamento condicional de imagens com tamanhos e atributos srcset diferentes, ou inclusão de subcomponentes interativos exclusivos.

#Estratégias de Performance para Repetidores Extensos

Em websites de alto tráfego com repetidores que ultrapassam 20 ou 30 iterações, a chamada sistemática de get_sub_field() pode gerar centenas de invocações da API do ACF. Para otimizar o tempo de processamento, recomendamos duas abordagens complementares:

#1. Leitura direta em lote com get_field()

Se o objetivo for apenas aceder aos dados sem manipular os ponteiros globais da página, pode capturar todo o array de linhas de uma só vez:

<?php
$itens = get_field( 'galeria_depoimentos' );

if ( ! empty( $itens ) && is_array( $itens ) ) {
    echo '<ul class="lista-depoimentos">';
    foreach ( $itens as $pos => $item ) {
        $par = ( $pos % 2 === 0 );
        $nome = esc_html( $item['nome_cliente'] ?? '' );
        $texto = esc_html( $item['texto_depoimento'] ?? '' );
        
        echo '<li class="' . ( $par ? 'tom-cinza' : 'tom-branco' ) . '">';
        echo '<strong>' . $nome . '</strong>: ' . $texto;
        echo '</li>';
    }
    echo '</ul>';
}

Esta técnica reduz a sobrecarga interna de controlo de estado do ACF, sendo especialmente eficiente em pipelines de renderização estática e temas headless.

#2. Cache com WordPress Transients

Para secções pesadas e com baixa frequência de alteração editorial, o resultado final do HTML gerado pelo repetidor pode ser colocado em cache através da Transients API:

<?php
$post_id = get_the_ID();
$chave_cache = 'wppoland_bloco_repeater_' . $post_id;
$html_armazenado = get_transient( $chave_cache );

if ( false === $html_armazenado ) {
    ob_start();
    // Executar o ciclo normal have_rows() aqui
    // ...
    $html_armazenado = ob_get_clean();
    
    // Guardar em cache durante 12 horas
    set_transient( $chave_cache, $html_armazenado, 12 * HOUR_IN_SECONDS );
}

echo $html_armazenado;

A invalidação do transient pode ser configurada automaticamente através do gancho acf/save_post ou save_post no momento em que o redator atualiza a publicação.

#Perguntas Frequentes sobre ACF Repeater

#Qual é a diferença entre get_row_index() e get_row_layout()?

A função get_row_index() devolve a posição numérica da linha dentro de um campo Repeater ou Flexible Content. Por sua vez, get_row_layout() é exclusiva do campo Flexible Content e retorna o identificador do layout ativo (por exemplo, bloco_texto ou bloco_galeria).

#O get_row_index() começa em 0 ou em 1?

No ACF, o get_row_index() inicia a contagem em 1. Se precisar de um índice baseado em zero (para correspondência direta com índices de arrays do JavaScript, por exemplo), basta subtrair 1 ao valor retornado: $indice_zero = get_row_index() - 1.

#Como evitar lentidão na base de dados com o ACF Repeater?

Limite o número de linhas por post a quantidades razoáveis. Quando necessitar de coleções com centenas de itens, prefira criar um Custom Post Type dedicado e relacioná-lo através de um campo Post Object ou Relationship, tirando proveito da paginação nativa e dos índices relacionais do MySQL.

Dominar a estrutura dos campos repetíveis do ACF permite construir interfaces dinâmicas, escaláveis e esteticamente impecáveis. Se o seu projeto necessita de arquitetura de dados à medida, migração de campos ou desenvolvimento de componentes avançados, conte com os nossos especialistas em desenvolvimento WordPress e ACF.

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Quer implementar isto no seu site?

Se quer transformar o artigo em melhorias concretas, redesign ou num plano de implementação, posso fechar o escopo e executar.

Cluster relacionado

Explorar outros serviços WordPress e base de conhecimento

Reforce o seu negócio com suporte técnico profissional em áreas-chave do ecossistema WordPress.

Artigos Relacionados