Advanced Custom Fields (ACF) - Wiederholbare Felder und Styling

Advanced Custom Fields (ACF) - Wiederholbare Felder und Styling

Zuletzt überprüft: 13. Oktober 2026
19 Min. Lesezeit
Fallstudie
Full-Stack-Entwickler
In der [professionellen WordPress-Theme-Entwicklung](/de/wordpress-entwickler/) gehört das Plugin Advanced Custom Fields (ACF Pro) zum festen Standard. Das Repeater Field (wiederholbares Feld) ist dabei eine der vielseitigsten Funktionen: Es erlaubt Redakteuren, beliebig viele strukturierte Einträge wie Team-Profile, FAQ-Akkordeons, Leistungsmerkmale oder Konferenz-Agenden direkt im Beitragseditor zu pflegen, ohne dafür separate Custom Post Types anlegen zu müssen.

Im Frontend stehen Entwickler jedoch regelmäßig vor der Herausforderung, diese dynamischen Zeilen gestalterisch zu variieren. Häufig verlangt das Screendesign ein sogenanntes Zick-Zack-Muster (Bild links, Text rechts im ersten Block; Bild rechts, Text links im zweiten Block) oder wechselnde Hintergrundfarben zur visuellen Rhythmisierung.

In dieser technischen Anleitung zeigen wir Ihnen, wie Sie die have_rows()-Schleife sauber strukturieren, wie Sie den Modulo-Operator in PHP und moderne CSS-Pseudoklassen :nth-child() gezielt einsetzen und wie Sie teure Performance-Fallen in der WordPress-Datenbank vermeiden.

#Das Datenmodell hinter dem ACF Repeater in der WordPress-Datenbank

Um sauberen und performanten Code zu schreiben, müssen Entwickler verstehen, wie WordPress und ACF die Werte auf Datenbankebene ablegen. WordPress speichert Beitragsmetadaten in der relationalen Tabelle wp_postmeta als einfache Schlüssel-Wert-Paare.

Wenn Sie ein Repeater-Feld mit dem Bezeichner leistungs_merkmale anlegen, das zwei Unterfelder (titel und beschreibung) enthält, und der Redakteur 4 Zeilen speichert, erzeugt ACF folgende Einträge:

  1. Einen Zähler-Datensatz: leistungs_merkmale mit dem Wert 4.
  2. Einträge für Zeile 0: leistungs_merkmale_0_titel und leistungs_merkmale_0_beschreibung.
  3. Einträge für Zeile 1: leistungs_merkmale_1_titel und leistungs_merkmale_1_beschreibung.
  4. …sowie interne Referenzschlüssel für die Felddefinitionen (_leistungs_merkmale_0_titel = field_65a12b8).

Für 10 Zeilen mit jeweils 3 Unterfeldern entstehen somit über 60 Zeilen in der Tabelle wp_postmeta. Bei unbedachter Nutzung in Schleifen kann dies zu spürbaren Verzögerungen führen, wenn WordPress viele Datenbankabfragen nacheinander absetzt.

#Wie funktioniert die ACF-Schleife mit have_rows und the_row

Die empfohlene Methode zur Ausgabe eines Repeater-Felds nutzt die Funktionen have_rows() und the_row():

<?php if ( have_rows('leistungs_merkmale') ) : ?>
    <section class="leistungen-repeater-wrapper" aria-label="<?php esc_attr_e('Unsere Leistungsmerkmale', 'wppoland'); ?>">
        <?php while ( have_rows('leistungs_merkmale') ) : the_row(); 
            // Unterfelder der aktuellen Zeile abrufen
            $titel = get_sub_field('titel');
            $beschreibung = get_sub_field('beschreibung');
            $grafik = get_sub_field('grafik');
        ?>
            <article class="leistung-karte">
                <?php if ( ! empty( $grafik ) && is_array( $grafik ) ) : ?>
                    <div class="leistung-bild">
                        <?php echo wp_get_attachment_image( (int) $grafik['id'], 'medium_large' ); ?>
                    </div>
                <?php endif; ?>
                <div class="leistung-inhalt">
                    <h3><?php echo esc_html( $titel ); ?></h3>
                    <div class="text"><?php echo wp_kses_post( $beschreibung ); ?></div>
                </div>
            </article>
        <?php endwhile; ?>
    </section>
<?php endif; ?>

Die Funktion the_row() rückt den internen Zeiger weiter und richtet den Datenkontext für get_sub_field() ein. Vermeiden Sie innerhalb der Schleife zwingend den Aufruf von get_field('titel'), da dieser versucht, ein globales Metadatum des Beitrags anstelle des zeilenbezogenen Unterfelds abzurufen.

#Zeilen-Styling in PHP mit get_row_index und Modulo-Logik

Wenn das Layout erfordert, dass jede zweite Zeile eine andere Hintergrundfarbe erhält, die Spaltenanordnung gespiegelt wird oder unterschiedliche CSS-Klassen vergeben werden, kommt serverseitige Logik zum Einsatz.

#Funktionsweise von get_row_index in ACF

Anstatt vor der Schleife eine manuelle Zählervariable ($i = 0) zu initialisieren, stellt ACF die native Funktion get_row_index() bereit. Diese gibt die Position der aktuellen Zeile als 1-basierten Index zurück (die erste Zeile liefert 1, die zweite 2 usw.):

<?php if ( have_rows('prozess_schritte') ) : ?>
    <div class="prozess-schritte-liste">
        <?php while ( have_rows('prozess_schritte') ) : the_row(); 
            $index = get_row_index();
            // Prüfung auf gerade Zeilen
            $ist_gerade = ( $index % 2 === 0 );
            $layout_klasse = $ist_gerade ? 'layout-gespiegelt' : 'layout-standard';
            $hintergrund   = $ist_gerade ? 'bg-hellgrau' : 'bg-weiss';
        ?>
            <div class="prozess-schritt <?php echo esc_attr( $layout_klasse . ' ' . $hintergrund ); ?>">
                <span class="schritt-nummer"><?php echo esc_html( sprintf( '%02d', $index ) ); ?></span>
                <div class="schritt-text">
                    <h4><?php echo esc_html( get_sub_field('titel') ); ?></h4>
                    <p><?php echo esc_html( get_sub_field('kurzbeschreibung') ); ?></p>
                </div>
            </div>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

#Jedes dritte Element im Raster hervorheben

Der PHP-Modulo-Operator (%) ermittelt den Rest einer ganzzahligen Division. Damit lassen sich vielfältige Rhythmen steuern:

  • Abwechselndes Zick-Zack-Muster: $index % 2 === 0
  • Hervorhebung jeder 3. Spalte im Grid: $index % 3 === 0
  • Sonderbehandlung des ersten und letzten Elements: Abgleich mit 1 bzw. der Gesamtzahl aller Zeilen.

Hier ein Beispiel zur Auszeichnung von Bestseller-Karten in einem dreispaltigen Raster:

$modulo_drei = $index % 3;
if ( $modulo_drei === 0 ) {
    $karten_klasse = 'karte-hervorgehoben karte-dritte';
} elseif ( $modulo_drei === 1 ) {
    $karten_klasse = 'karte-standard karte-erste';
} else {
    $karten_klasse = 'karte-standard karte-zweite';
}

#Sichere Kennzeichnung der ersten und letzten Zeile

Um das erste Element speziell auszuzeichnen oder beim letzten Element eine störende Trennlinie zu entfernen, vergleichen Sie den Index mit der Zeilenanzahl:

<?php 
$rohdaten = get_field('team_mitglieder');
$gesamtanzahl = is_array( $rohdaten ) ? count( $rohdaten ) : 0;

if ( have_rows('team_mitglieder') ) : ?>
    <div class="team-raster">
        <?php while ( have_rows('team_mitglieder') ) : the_row(); 
            $aktuelle_zeile = get_row_index();
            $klassen = ['team-eintrag'];

            if ( $aktuelle_zeile === 1 ) {
                $klassen[] = 'eintrag-erster';
            }
            if ( $aktuelle_zeile === $gesamtanzahl ) {
                $klassen[] = 'eintrag-letzter';
            }
            if ( $aktuelle_zeile % 2 === 0 ) {
                $klassen[] = 'zeile-gerade';
            } else {
                $klassen[] = 'zeile-ungerade';
            }
        ?>
            <div class="<?php echo esc_attr( implode( ' ', $klassen ) ); ?>">
                <p class="name"><?php echo esc_html( get_sub_field('mitarbeiter_name') ); ?></p>
                <p class="position"><?php echo esc_html( get_sub_field('mitarbeiter_position') ); ?></p>
            </div>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

#Unterfeld-Logik und dynamische Layout-Variationen

Das Styling muss sich nicht ausschließlich an numerischen Positionen orientieren. Wenn Redakteure im Backend aus Dropdowns verschiedene Darstellungsstile wählen können, kombiniert der Code Zeilenindizes mit Unterfeldwerten:

<?php if ( have_rows('fallstudien_abschnitte') ) : ?>
    <div class="fallstudien-strom">
        <?php while ( have_rows('fallstudien_abschnitte') ) : the_row(); 
            $zeilen_index = get_row_index();
            $darstellung  = get_sub_field('layout_variante'); // 'vollbreite', 'split', 'kompakt'
            $akzent_farbe = get_sub_field('akzent_farbe');

            $wrapper_klassen = [
                'abschnitt-karte',
                'zeile-' . $zeilen_index,
                'variante-' . sanitize_html_class( $darstellung ?: 'standard' )
            ];

            if ( $zeilen_index % 2 === 0 ) {
                $wrapper_klassen[] = 'spalten-gespiegelt';
            }
        ?>
            <section 
                class="<?php echo esc_attr( implode( ' ', $wrapper_klassen ) ); ?>"
                style="<?php echo $akzent_farbe ? esc_attr( '--zeilen-akzent: ' . sanitize_hex_color( $akzent_farbe ) . ';' ) : ''; ?>"
            >
                <?php 
                if ( $darstellung === 'vollbreite' && $zeilen_index === 1 ) {
                    get_template_part( 'template-parts/fallstudien/hero' );
                } else {
                    get_template_part( 'template-parts/fallstudien/karte' );
                }
                ?>
            </section>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

Durch die Übergabe von CSS-Variablen (--zeilen-akzent) und sanitized Klassen bleibt der HTML-Code flexibel und von starren Vorgaben entkoppelt.

#Wann CSS nth-child anstelle von PHP verwendet werden sollte

Nach dem Grundsatz sauberer Software-Architektur sollte Präsentationslogik im Stylesheet verbleiben, solange keine semantischen HTML-Änderungen vorliegen. Wenn lediglich Farben, Abstände oder Flexbox-Reihenfolgen variiert werden, ist CSS :nth-child() performanter und wartungsfreundlicher:

/* Zebra-Streifen für abwechselnde Zeilen */
.prozess-schritte-liste .prozess-schritt:nth-child(even) {
  background-color: #f8fafc;
}

/* Umkehrung der Flexbox-Richtung in geraden Blöcken */
.prozess-schritte-liste .prozess-schritt {
  display: flex;
  flex-direction: row;
  gap: 2rem;
  align-items: center;
}

.prozess-schritte-liste .prozess-schritt:nth-child(even) {
  flex-direction: row-reverse;
}

/* Hervorhebung jeder dritten Karte im CSS-Grid */
.leistungen-repeater-wrapper .leistung-karte:nth-child(3n) {
  border: 2px solid #2563eb;
  transform: scale(1.02);
}

/* Erstes und letztes Element */
.prozess-schritte-liste .prozess-schritt:first-child {
  border-top: 3px solid #0f172a;
}

.prozess-schritte-liste .prozess-schritt:last-child {
  border-bottom: 3px solid #0f172a;
}

#Entscheidungsmatrix: PHP oder CSS

  • CSS :nth-child(): Reine Farbänderungen, Typografieanpassungen, Randgestaltungen, responsive Flexbox-Umkehrung via Media Queries.
  • PHP Modulo / Zähler: Unterschiedliche HTML-Elemente (<aside> statt <section>), Laden separater Template-Dateien via get_template_part(), Ausgabe von Schema.org JSON-LD Positionsmetadaten.

#Integration mit ACF Flexible Content für modulare Seitenstrukturen

Wenn wiederholte Abschnitte völlig unterschiedliche Feldstrukturen erfordern, führt ein überladenes Repeater-Feld zu unübersichtlichen Eingabemasken. Das Flexible Content Feld stellt hierfür das passende Architekturmuster dar:

<?php if ( have_rows('seiten_bausteine') ) : ?>
    <main class="bausteine-container">
        <?php while ( have_rows('seiten_bausteine') ) : the_row(); 
            $layout_typ = get_row_layout();
            $index      = get_row_index();
        ?>
            <section class="baustein baustein-<?php echo esc_attr( $layout_typ ); ?>" data-position="<?php echo (int) $index; ?>">
                <?php 
                if ( $layout_typ === 'hero_buehne' ) :
                    get_template_part( 'template-parts/module/hero', null, [ 'index' => $index ] );
                elseif ( $layout_typ === 'vorteile_raster' ) :
                    get_template_part( 'template-parts/module/vorteile', null, [ 'index' => $index ] );
                elseif ( $layout_typ === 'kunden_meinungen' ) :
                    get_template_part( 'template-parts/module/testimonials', null, [ 'index' => $index ] );
                endif;
                ?>
            </section>
        <?php endwhile; ?>
    </main>
<?php endif; ?>

Die Funktion get_row_layout() sorgt für klare Verantwortlichkeiten im Theme und verhindert ungenutzte Datenbankeinträge für irrelevante Unterfelder.

#Gutenberg ACF Blöcke mit InnerBlocks und Block Context

In modernen Block-Themes ersetzen ACF-Gutenberg-Blöcke starre Backend-Metaboxen. Anstelle monolithischer Repeater-Strukturen setzt modernes WordPress-Engineering auf Container-Blöcke mit InnerBlocks und Block Context.

#Block-Registrierung via block.json

In der block.json des Kind-Blocks wird die Eltern-Kind-Beziehung sowie der Kontext deklariert:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "wppoland/prozess-karte",
  "title": "Prozess Karte",
  "category": "design",
  "parent": [ "wppoland/prozess-container" ],
  "usesContext": [ "wppoland/rasterLayout" ],
  "acf": {
    "mode": "preview",
    "renderTemplate": "render.php"
  }
}

#Container- und Item-Rendering

Im Rendering-Template des übergeordneten Blocks (prozess-container/render.php) binden wir den InnerBlocks-Bereich ein:

<div class="prozess-container-block">
    <InnerBlocks 
        allowedBlocks="<?php echo esc_attr( wp_json_encode( ['wppoland/prozess-karte'] ) ); ?>"
        template="<?php echo esc_attr( wp_json_encode( [ ['wppoland/prozess-karte'], ['wppoland/prozess-karte'] ] ) ); ?>"
    />
</div>

Im Template des Kind-Blocks (prozess-karte/render.php) greifen wir direkt auf den übergebenen Kontext zu:

<?php
$raster = $context['wppoland/rasterLayout'] ?? 'spalten-3';
$titel  = get_field('schritt_titel');
$text   = get_field('schritt_beschreibung');
?>
<div class="prozess-karte-item layout-<?php echo esc_attr( $raster ); ?>">
    <h4><?php echo esc_html( $titel ); ?></h4>
    <div><?php echo wp_kses_post( $text ); ?></div>
</div>

Diese Architektur ermöglicht es Redakteuren, einzelne Karten visuell im Gutenberg-Editor per Drag-and-Drop neu anzuordnen und individuell zu formatieren.

#Performance-Optimierung und Datenbankstrategien bei großen Repeater-Feldern

Tief verschachtelte Repeater-Felder belasten die Tabelle wp_postmeta erheblich. Enthält ein äußerer Repeater 15 Zeilen und jede Zeile einen inneren Repeater mit 10 Einträgen à 3 Unterfeldern, speichert WordPress über 500 Metadatensätze für einen einzigen Beitrag.

#1. Datenabruf als Array via get_field

Anstelle der standardmäßigen while( have_rows() )-Schleife, die bei jedem Durchlauf interne Pointer aktualisiert, ist bei sehr großen Datenmengen das einmalige Auslesen als PHP-Array performanter:

$alle_schritte = get_field('prozess_schritte');

if ( ! empty( $alle_schritte ) && is_array( $alle_schritte ) ) {
    echo '<div class="direkt-array-liste">';
    foreach ( $alle_schritte as $null_index => $zeile ) {
        $nummer = $null_index + 1;
        $klasse = ( $nummer % 2 === 0 ) ? 'eintrag-gerade' : 'eintrag-ungerade';
        $titel  = $zeile['titel'] ?? '';
        $text   = $zeile['kurzbeschreibung'] ?? '';

        echo '<div class="' . esc_attr( $klasse ) . '">';
        echo '<h4>' . esc_html( $titel ) . '</h4>';
        echo '<p>' . esc_html( $text ) . '</p>';
        echo '</div>';
    }
    echo '</div>';
}

#2. Caching des fertigen HTML mit der Transients API

Für aufwendige Sektionen, die sich nur selten ändern, empfiehlt sich das Zwischenspeichern des fertig gerenderten HTML-Codes:

function wppoland_get_cached_leistungen( int $post_id ): string {
    $cache_key = 'wppoland_leistungen_html_' . $post_id;
    $html      = get_transient( $cache_key );

    if ( false !== $html ) {
        return $html;
    }

    if ( ! have_rows('leistungs_merkmale', $post_id) ) {
        return '';
    }

    ob_start();
    echo '<div class="leistungen-grid">';
    while ( have_rows('leistungs_merkmale', $post_id) ) {
        the_row();
        $i = get_row_index();
        $klasse = ( $i % 2 === 0 ) ? 'karte-gerade' : 'karte-ungerade';
        echo '<div class="' . esc_attr( $klasse ) . '">';
        echo '<h3>' . esc_html( get_sub_field('titel') ) . '</h3>';
        echo '<div>' . wp_kses_post( get_sub_field('beschreibung') ) . '</div>';
        echo '</div>';
    }
    echo '</div>';
    $html = ob_get_clean();

    // Cache für 24 Stunden (86400 Sekunden) anlegen
    set_transient( $cache_key, $html, 86400 );

    return $html;
}

// Cache-Invalidierung bei Beitragsaktualisierung
add_action('save_post', function( int $post_id ): void {
    if ( ! wp_is_post_revision( $post_id ) && ! wp_is_post_autosave( $post_id ) ) {
        delete_transient( 'wppoland_leistungen_html_' . $post_id );
    }
});

Dadurch reduziert sich die Ladezeit umfangreicher Seiten auf wenige Millisekunden.

#REST API und Headless WordPress Optimierung von Repeater-Daten

In Headless-WordPress-Architekturen mit Frameworks wie Next.js oder Astro werden ACF-Repeater-Daten über die WP REST API ausgeliefert. Standardmäßig enthält die API-Antwort unformatierte Rohdaten. Durch register_rest_field lässt sich die Antwort serverseitig schlank und konsumierbar aufbereiten:

add_action('rest_api_init', function(): void {
    register_rest_field('page', 'leistungen_optimiert', [
        'get_callback' => function( array $post_arr ): array {
            $post_id = $post_arr['id'];
            $zeilen  = get_field('leistungs_merkmale', $post_id);

            if ( empty( $zeilen ) || ! is_array( $zeilen ) ) {
                return [];
            }

            return array_map( function( array $zeile, int $key ): array {
                return [
                    'index'     => $key + 1,
                    'istGerade' => ( ( $key + 1 ) % 2 === 0 ),
                    'titel'     => sanitize_text_field( $zeile['titel'] ?? '' ),
                    'text'      => wp_kses_post( $zeile['beschreibung'] ?? '' ),
                ];
            }, $zeilen, array_keys( $zeilen ) );
        },
        'schema' => [
            'description' => 'Optimierte Leistungsmerkmale für Headless Frontend.',
            'type'        => 'array',
        ],
    ]);
});

#Fehlersuche und typische Stolperfallen in Repeater-Schleifen

Bei der Implementierung von ACF-Repeater-Feldern treten regelmäßig typische Entwicklerfehler auf:

  1. Endlosschleifen durch fehlendes the_row(): Wenn der Aufruf von the_row() in der while-Bedingung oder direkt danach vergessen wird, verbleibt der Schleifenzeiger dauerhaft auf der ersten Zeile, was das Skript zum Absturz bringt.
  2. Leere Ausgaben durch get_field() anstelle von get_sub_field(): Der Aufruf von get_field() innerhalb einer Repeater-Zeile sucht im übergeordneten Beitrag nach dem Feldnamen und schlägt fehl.
  3. Typ-Fehler bei count() in PHP 8: Wenn vor count( get_field('mein_repeater') ) nicht geprüft wird, ob der Rückgabewert ein Array ist, bricht PHP 8 mit einem TypeError ab.
  4. CSS nth-child greift nicht wegen Slider-Wrapper: Wenn JavaScript-Bibliotheken (z. B. Swiper) zusätzliche Hilfs-Divs erzeugen, passen die CSS-Selektoren :nth-child() nicht mehr zu den logischen Kindelementen. In solchen Fällen ist die Vergabe von PHP-Klassen die sicherere Wahl.

#Komponentenbasierte Theme-Architektur mit get_template_part und Argumenten

In modernen Enterprise-Themes wird Vermischung von Datenbanklogik und HTML-Auszeichnung in einer einzigen Datei vermieden. Stattdessen wird die Ausgabe einzelner Zeilen in wiederverwendbare Komponenten (Template Parts) ausgelagert. Seit WordPress 5.5 unterstützt get_template_part() ein drittes Argument $args, über das strukturierte Daten typsicher an die Teil-Vorlage übergeben werden.

<?php if ( have_rows('leistung_elemente') ) : ?>
    <div class="leistungen-raster-grid">
        <?php while ( have_rows('leistung_elemente') ) : the_row(); 
            $zeilen_nummer = get_row_index();
            $karten_daten  = [
                'index'       => $zeilen_nummer,
                'ist_gerade'  => ( $zeilen_nummer % 2 === 0 ),
                'titel'       => get_sub_field('element_titel'),
                'text'        => get_sub_field('element_beschreibung'),
                'icon'        => get_sub_field('element_icon'),
            ];

            get_template_part( 'template-parts/komponenten/leistung-karte', null, $karten_daten );
        ?>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

In der Komponentendatei template-parts/komponenten/leistung-karte.php greifen Sie sauber auf das $args-Array zu:

<?php
/**
 * Teilvorlage fuer einzelne Leistungskarte.
 *
 * @var array $args Uebergebene Zeilendaten.
 */
$index      = (int) ( $args['index'] ?? 1 );
$ist_gerade = (bool) ( $args['ist_gerade'] ?? false );
$titel      = $args['titel'] ?? '';
$text       = $args['text'] ?? '';
$icon       = $args['icon'] ?? null;

$karten_klasse = $ist_gerade ? 'leistung-element stil-gerade' : 'leistung-element stil-ungerade';
?>
<article class="<?php echo esc_attr( $karten_klasse ); ?>" data-position="<?php echo esc_attr( $index ); ?>">
    <?php if ( ! empty( $icon['id'] ) ) : ?>
        <div class="element-icon">
            <?php echo wp_get_attachment_image( (int) $icon['id'], 'thumbnail' ); ?>
        </div>
    <?php endif; ?>
    <h3 class="element-titel"><?php echo esc_html( $titel ); ?></h3>
    <div class="element-text"><?php echo wp_kses_post( $text ); ?></div>
</article>

Durch diese Trennung kann das Erscheinungsbild der Karte unabhängig vom Kontext getestet und überarbeitet werden, ohne dass Anpassungen an der umschließenden Schleife vorgenommen werden müssen.

#Verschachtelte Repeater-Felder sauber und fehlersicher implementieren

Häufig erfordern Kundenprojekte hierarchische Strukturen wie beispielsweise eine Liste von Firmenstandorten, wobei an jedem Standort mehrere Ansprechpartner aufgeführt sind. In ACF wird dies durch einen verschachtelten Repeater (Nested Repeater) abgebildet.

<?php if ( have_rows('unternehmens_standorte') ) : ?>
    <div class="standorte-uebersicht">
        <?php while ( have_rows('unternehmens_standorte') ) : the_row(); 
            $standort_index = get_row_index();
            $stadt_name     = get_sub_field('stadt');
        ?>
            <section class="standort-abschnitt standort-<?php echo esc_attr( $standort_index ); ?>">
                <h2 class="stadt-titel"><?php echo esc_html( $stadt_name ); ?></h2>

                <?php if ( have_rows('ansprechpartner') ) : ?>
                    <ul class="partner-liste">
                        <?php while ( have_rows('ansprechpartner') ) : the_row(); 
                            $partner_index = get_row_index();
                            $name          = get_sub_field('name');
                            $abteilung     = get_sub_field('abteilung');
                            $zeilen_stil   = ( $partner_index % 2 === 0 ) ? 'kontakt-zeile-b' : 'kontakt-zeile-a';
                        ?>
                            <li class="partner-eintrag <?php echo esc_attr( $zeilen_stil ); ?>">
                                <strong><?php echo esc_html( $name ); ?></strong>
                                <span><?php echo esc_html( $abteilung ); ?></span>
                            </li>
                        <?php endwhile; ?>
                    </ul>
                <?php endif; ?>
            </section>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

In diesem Kontext liefert get_row_index() innerhalb der inneren Schleife ansprechpartner die relative Position des Kontakts innerhalb des jeweiligen Standorts. So lassen sich lokale Zebra-Muster und Zähler problemlos umsetzen.

#Praxislösung: Preistabelle mit flexiblen Karten und Empfehlungs-Badge

Als zusammenfassendes Praxisbeispiel implementieren wir eine vollständige Preistabelle. Sie demonstriert die Kombination von Index-Prüfung, Modulo-Logik und benutzerdefinierten Schaltern:

<?php if ( have_rows('preis_staffeln') ) : 
    $rohdaten = get_field('preis_staffeln');
    $anzahl   = is_array( $rohdaten ) ? count( $rohdaten ) : 0;
?>
    <section class="preise-sektion" id="preise" aria-label="<?php esc_attr_e('Preistabelle', 'wppoland'); ?>">
        <div class="preis-raster">
            <?php while ( have_rows('preis_staffeln') ) : the_row(); 
                $pos          = get_row_index();
                $tarif_name   = get_sub_field('tarif_name');
                $preis_wert   = get_sub_field('preis_wert');
                $ist_bestseller = (bool) get_sub_field('als_empfehlung_markieren');
                $merkmale     = get_sub_field('merkmale_liste');

                $klassen = [ 'preis-karte', 'pos-' . $pos ];
                if ( $ist_bestseller ) {
                    $klassen[] = 'karte-hervorgehoben';
                }
                if ( $pos === 1 ) {
                    $klassen[] = 'karte-basis';
                }
                if ( $pos === $anzahl ) {
                    $klassen[] = 'karte-enterprise';
                }
                $klassen[] = ( $pos % 2 === 0 ) ? 'schattierung-gerade' : 'schattierung-ungerade';
            ?>
                <div class="<?php echo esc_attr( implode( ' ', $klassen ) ); ?>">
                    <?php if ( $ist_bestseller ) : ?>
                        <span class="badge-empfehlung"><?php esc_html_e('Beliebteste Wahl', 'wppoland'); ?></span>
                    <?php endif; ?>

                    <h3 class="tarif-titel"><?php echo esc_html( $tarif_name ); ?></h3>
                    <div class="preis-anzeige"><?php echo esc_html( $preis_wert ); ?></div>

                    <?php if ( ! empty( $merkmale ) && is_array( $merkmale ) ) : ?>
                        <ul class="merkmale-auflistung">
                            <?php foreach ( $merkmale as $punkt ) : ?>
                                <li><?php echo esc_html( $punkt['merkmal_text'] ?? '' ); ?></li>
                            <?php endforeach; ?>
                        </ul>
                    <?php endif; ?>

                    <div class="karte-aktion">
                        <a href="#anfrage" class="button-buchen"><?php esc_html_e('Jetzt starten', 'wppoland'); ?></a>
                    </div>
                </div>
            <?php endwhile; ?>
        </div>
    </section>
<?php endif; ?>

#Häufig gestellte Fragen zu ACF Repeater Styling

#Wie unterscheidet sich get_row_index von einem manuellen Zähler

Die Funktion get_row_index() ist eine native ACF-Methode, die automatisch die 1-basierte Position der aktuellen Zeile zurückgibt. Entwickler sparen sich damit das manuelle Initialisieren und Inkrementieren von Zählervariablen vor und innerhalb der Schleife.

#Beginnt get_row_index bei 0 oder 1

In ACF beginnt get_row_index() immer bei 1 für die erste Zeile. Für Modulo-Berechnungen von geraden und ungeraden Zeilen wird ($index % 2 === 0) für gerade Zeilen und ($index % 2 !== 0) für ungerade Zeilen geprüft.

#Wann sollte Styling in CSS statt PHP erfolgen

Reine visuelle Anpassungen wie Hintergrundfarben, Ränder oder Flexbox-Umkehrungen gehören in CSS über die Pseudoklassen :nth-child(even) und :nth-child(odd). PHP ist erforderlich, wenn unterschiedliche HTML-Strukturen gerendert oder verschiedene Template-Dateien geladen werden müssen.

#Warum verlangsamt ein großes Repeater-Feld die Datenbank

ACF speichert jedes Unterfeld jeder Zeile als eigenständigen Eintrag in der Tabelle wp_postmeta. Bei vielen Zeilen summiert sich dies zu Hunderten von Datenbankzeilen pro Beitrag. Abhilfe schaffen Caching mit Transients, der Direktabruf via get_field() oder die Umstellung auf ACF Gutenberg Blöcke.

#Typensichere Datenverarbeitung und Sanitization in der Theme-Ausgabe

Beim Rendern von Benutzereingaben aus Repeater-Feldern muss die Ausgabesicherheit an oberster Stelle stehen. Selbst in geschlossenen Redaktionsumgebungen schützt strikte Ausgabebereinigung vor versehentlichen Formatierungsfehlern und Cross-Site-Scripting (XSS).

  1. Einfache Textfelder: Nutzen Sie esc_html() für Überschriften, Zähler und Beschriftungen.
  2. HTML-Attribute: Attributwerte wie Klassen, IDs oder Linkziele müssen durch esc_attr() und esc_url() abgesichert werden.
  3. Formatierter Text: Textbereiche mit WYSIWYG-Editor sollten stets mit wp_kses_post() ausgegeben werden, um bösartige Skripte herauszufiltern und gleichzeitig erlaubte HTML-Tags wie Absätze und Listen beizubehalten.
  4. Bilder und Medienanhänge: Konfigurieren Sie Unterfelder vom Typ Bild im ACF-Backend immer so, dass die Bild-ID zurückgegeben wird. Das Rendern über wp_get_attachment_image() generiert automatisch moderne srcset-Attribute und optimierte Ladezeiten.
$kontakt_link = get_sub_field('button_url');
$link_text    = get_sub_field('button_text');

if ( ! empty( $kontakt_link ) ) : ?>
    <a href="<?php echo esc_url( $kontakt_link ); ?>" class="btn-interaktion" target="_blank" rel="noopener noreferrer">
        <?php echo esc_html( $link_text ?: __( 'Mehr erfahren', 'wppoland' ) ); ?>
    </a>
<?php endif; ?>

#CSS-Grid Architekturen für flexible Repeater-Karten

Um im Screendesign auf verschiedene Bildschirmgrößen flexibel zu reagieren, empfiehlt sich modernes CSS Grid in Kombination mit minmax() und auto-fit. Dadurch passen sich die Karten ohne starre Breakpoint-Deflation automatisch an:

.preis-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 2rem;
  align-items: stretch;
}

.preis-karte {
  border-radius: 12px;
  padding: 2rem;
  background: #ffffff;
  border: 1px solid #e2e8f0;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.preis-karte:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px -8px rgba(15, 23, 42, 0.12);
}

.preis-karte.karte-hervorgehoben {
  border: 2px solid #2563eb;
  position: relative;
  box-shadow: 0 20px 25px -5px rgba(37, 99, 235, 0.1);
}

.preis-karte.schattierung-gerade {
  background-color: #f8fafc;
}

Durch diesen Aufbau bleibt das Layout auf Desktop-Monitoren dreispaltig, bricht auf Tablets automatisch auf zwei Spalten um und wird auf Smartphones einspaltig dargestellt, ohne dass Anpassungen am PHP-Code erforderlich sind.

#Performance-Messung und Profiling von Datenbankabfragen in wp_postmeta

Um die tatsächliche Belastung durch umfangreiche Repeater-Felder auf Serverebene objektiv zu bewerten, sollten Entwickler strukturierte Profiling-Tools wie das Plugin Query Monitor oder PHP-Profiling mit Xdebug einsetzen.

Beim Laden einer WordPress-Seite ohne Caching führt WordPress standardmäßig einen Aufruf an wp_load_alloptions() und separate Metadatenabfragen aus. Wenn eine Beitragsseite aufgerufen wird, liest die Funktion update_postmeta_cache() alle Metadaten des aktuellen Beitrags in den internen WordPress-Objekt-Cache ein.

#Warum entstehen dennoch Performance-Engpässe bei tiefen Repeatern

  1. Massive Serialisierung und Speicherverbrauch: Obwohl WordPress die Metadaten in einer einzigen Datenbankabfrage liest, muss ACF für jedes einzelne Unterfeld interne Filter und Feldobjekte instanziieren (acf_get_field()). Bei 100 Unterfeldern bedeutet dies 100 interne Lookups, die PHP-Ausführungszeit und RAM beanspruchen.
  2. Begrenzungen des MySQL-Buffers: Auf Shared-Hosting-Servern oder knapp bemessenen VPS-Instanzen können große Metadaten-Arrays den PHP-Speicher (memory_limit) erschöpfen, insbesondere wenn Bilder und Taxonomie-Beziehungen in jedem Unterfeld verschachtelt sind.
  3. Objekt-Cache-Serialisierung in Redis: Wenn Redis als persistenter Objekt-Cache genutzt wird, führt das Speichern und Laden von 500 KB großen serialisierten Arrays pro Beitragsaufruf zu Netzwerk-Overhead zwischen Webserver und Cache-Daemon.

Durch den gezielten Einsatz von Fragment-Caching (Transients) oder die Restrukturierung in ACF Gutenberg-Blöcke wird dieser Overhead vermieden, da nur die tatsächlich im Viewport benötigten HTML-Fragmente gerendert werden.

#Fazit und Best Practices Checkliste

Das ACF Repeater-Feld bietet Redakteuren Flexibilität und Entwicklern eine saubere Datenstruktur. Durch das Zusammenspiel von get_row_index(), Modulo-Logik und modernen CSS-Pseudoklassen :nth-child() lassen sich anspruchsvolle Layouts sauber und wartungsfreundlich umsetzen.

Beachten Sie bei Ihren Projekten folgende Leitlinien:

  1. Schleifen sauber absichern: Immer mit have_rows() prüfen und the_row() korrekt aufrufen.
  2. Unterfelder isolieren: Innerhalb der Schleife ausschließlich get_sub_field() nutzen.
  3. Präsentationslogik trennen: Reine Optik in CSS belassen, strukturelle Unterschiede in PHP steuern.
  4. Datenbank schonen: Bei großen Datenmengen Transients-Caching implementieren oder zu Gutenberg-Blöcken mit InnerBlocks migrieren.

Möchten Sie komplexe WordPress-Architekturen optimieren oder maßgeschneiderte Gutenberg-Themes entwickeln lassen? Profitieren Sie von unserer Expertise im Bereich WordPress-Entwicklung und technischer WordPress-Wartung.

Nächster Schritt

Machen Sie aus dem Artikel eine echte Umsetzung

Dieser Block stärkt die interne Verlinkung und führt Nutzer gezielt zum nächsten sinnvollen Schritt im Service- und Content-System.

Soll das Thema auf Ihrer Website umgesetzt werden?

Wenn Sie aus dem Artikel konkrete Maßnahmen für Website, Relaunch oder Weiterentwicklung ableiten wollen, definiere ich den Scope und setze ihn um.

Relevanter Cluster

Weitere WordPress-Dienste und Wissensbasis entdecken

Stärken Sie Ihr Unternehmen mit professionellem technischen Support in den Kernbereichen des WordPress-Ökosystems.

Ähnliche Artikel