Live-Search-Autocomplete im Hyvä-Header integrieren
AI generated
Hyvä
phtml
Hyvä · Live-Search · Alpine.js · GraphQL
Live-Search-Autocomplete im Hyvä-Header integrieren
von der GraphQL-Query bis zur barrierefreien Alpine-Komponente

Die Standard-Suchbox von Hyvä springt erst nach Enter zur Ergebnisliste und zeigt während der Eingabe keine Vorschläge. Live-Search-Autocomplete im Hyvä-Header schließt genau diese Lücke: eine GraphQL-Suggestion-Query liefert Produkte, Kategorien und Preise, eine schlanke Alpine.js-Komponente steuert Debounce, Request-Cancellation und Tastatur-Navigation, und ein CSP-konformes Rendering hält den Shop dabei sicher und performant.

17 Min. Lesezeit GraphQL · Alpine.js · AbortController · ARIA Magento 2.4.8-p4 · Hyvä · PHP 8.4

1. Ausgangslage: Standard-Suche vs. Live-Search-Autocomplete

Hyvä liefert im Grundzustand eine schlichte Quick-Search-Box, die auf den Standard-Magento-Suchcontroller zeigt und erst bei Enter zur Ergebnisliste springt, ohne echte Vorschläge während der Eingabe. Für viele Shops reicht das nicht: Kunden erwarten aus dem Amazon- und Google-Alltag, dass beim Tippen sofort passende Produkte, Kategorien und Preise erscheinen. Genau diese Lücke schließt Live-Search-Autocomplete im Hyvä-Header, eine erweiterte Suche, die per GraphQL im Hintergrund Vorschläge nachlädt, während der Nutzer noch tippt.

Wer Adobe Live Search oder eine Elasticsearch- beziehungsweise OpenSearch-basierte Suche im Einsatz hat, bekommt über die GraphQL-Schnittstelle bereits relevanzsortierte Ergebnisse, muss aber selbst entscheiden, wie diese Daten im Frontend ankommen. Ohne eigene Autocomplete-Komponente bleibt die Leistungsfähigkeit der Suchengine ungenutzt, weil der Header weiterhin nur ein einfaches Eingabefeld zeigt. Autocomplete im Hyvä-Header verbindet die leistungsfähige Backend-Suche mit einer Frontend-Komponente, die Vorschläge, Tastatur-Navigation und Barrierefreiheit sauber zusammenführt.

Der Aufwand für eine solche Erweiterung ist überschaubar, wenn man sich an Hyväs bestehende Struktur hält: Statt die Suche komplett neu zu bauen, wird die vorhandene header-search-Komponente um Alpine.js-State, eine GraphQL-Query und ein paar CSP-konforme Script-Blöcke ergänzt. Dieser Artikel zeigt genau diesen Weg, von der Architektur über die Query bis zur barrierefreien Live-Search-Autocomplete-Komponente im Hyvä-Header.

2. Architektur der Kopfzeilen-Suche in Hyvä

Die Kopfzeilen-Suche liegt in Hyvä standardmäßig unter templates/header/search.phtml innerhalb des Magento_Search-Moduls des Themes und wird über header.phtml als Child-Block eingebunden. Für Live-Search-Autocomplete im Hyvä-Header überschreibt man diese Datei im eigenen Theme und ersetzt das reine Eingabefeld durch einen Alpine-x-data-Container, der Query-String, Ergebnisliste, Ladezustand und aktiven Index verwaltet.

Layout-technisch bindet man die Autocomplete-Komponente über default.xml beziehungsweise ein Block-Reference im Header-Container ein, sodass die Reihenfolge der Kindelemente unverändert bleibt und $block->getChildNames() weiterhin zuverlässig iteriert. Ein eigenes ViewModel liefert Konfigurationswerte wie Mindestzeichenzahl, Debounce-Zeit und maximale Trefferanzahl aus dem Adminbereich an die phtml-Datei, statt Werte hart im Template zu verdrahten.

Der folgende Ausschnitt zeigt die grundlegende Struktur: ein x-data-Wurzelelement mit combobox-Rolle, ein Eingabefeld mit ARIA-Attributen und eine Ergebnisliste, die erst im offenen Zustand sichtbar wird. Diese Struktur bildet das Grundgerüst, auf dem alle weiteren Abschnitte dieser Live-Search-Autocomplete-Integration aufbauen.


<?php
/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
/** @var \Mironsoft\SearchAutocomplete\ViewModel\SearchAutocompleteConfig $searchConfig */
$hyvaCsp = $viewModels->require(\Hyva\Theme\ViewModel\HyvaCsp::class);
$searchConfig = $viewModels->require(\Mironsoft\SearchAutocomplete\ViewModel\SearchAutocompleteConfig::class);
?>
<div class="relative"
     x-data="hyvaLiveSearchAutocomplete({
         minChars: <?= (int) $searchConfig->getMinQueryLength() ?>,
         debounceMs: <?= (int) $searchConfig->getDebounceMs() ?>,
         maxSuggestions: <?= (int) $searchConfig->getMaxSuggestions() ?>
     })"
     @click.outside="closeSuggestions()"
     @keydown.escape.window="closeSuggestions()">

    <label for="live-search-autocomplete-input" class="sr-only">
        <?= $escaper->escapeHtml(__('Search')) ?>
    </label>

    <input
        id="live-search-autocomplete-input"
        type="search"
        name="q"
        autocomplete="off"
        role="combobox"
        aria-controls="live-search-autocomplete-listbox"
        aria-autocomplete="list"
        :aria-expanded="open ? 'true' : 'false'"
        :aria-activedescendant="activeIndex > -1 ? 'suggestion-' + activeIndex : null"
        x-model="query"
        @input.debounce="fetchSuggestions()"
        @keydown.arrow-down.prevent="moveActive(1)"
        @keydown.arrow-up.prevent="moveActive(-1)"
        @keydown.enter.prevent="selectActive()"
        class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-500"
        placeholder="<?= $escaper->escapeHtmlAttr(__('Search products...')) ?>"
    >

    <ul
        id="live-search-autocomplete-listbox"
        role="listbox"
        x-show="open && (results.length > 0 || !loading)"
        x-cloak
        class="absolute z-30 mt-2 w-full rounded-xl border border-gray-200 bg-white shadow-lg"
    >
        <template x-for="(item, index) in results" :key="item.sku">
            <li
                :id="'suggestion-' + index"
                role="option"
                :aria-selected="index === activeIndex"
                @mouseenter="activeIndex = index"
                @click="selectResult(item)"
                :class="{ 'bg-orange-50': index === activeIndex }"
                class="flex items-center gap-3 px-4 py-2 cursor-pointer"
            >
                <img :src="item.thumbnail" :alt="item.name" class="h-10 w-10 object-cover rounded" loading="lazy">
                <div class="flex flex-col">
                    <span class="text-sm text-gray-800" x-text="item.name"></span>
                    <span class="text-xs text-gray-500" x-text="item.price"></span>
                </div>
            </li>
        </template>
    </ul>
</div>

Das ViewModel dahinter folgt dem in Hyvä üblichen Muster einer schlanken Klasse, die ArgumentInterface implementiert und ausschließlich Konfigurationswerte bereitstellt, keine Geschäftslogik und keine direkten GraphQL-Aufrufe.


<?php

declare(strict_types=1);

namespace Mironsoft\SearchAutocomplete\ViewModel;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Store\Model\ScopeInterface;

/**
 * Provides Live Search autocomplete configuration values to the header search template.
 */
class SearchAutocompleteConfig implements ArgumentInterface
{
    private const XML_PATH_MIN_QUERY_LENGTH = 'mironsoft_searchautocomplete/general/min_query_length';
    private const XML_PATH_DEBOUNCE_MS = 'mironsoft_searchautocomplete/general/debounce_ms';
    private const XML_PATH_MAX_SUGGESTIONS = 'mironsoft_searchautocomplete/general/max_suggestions';

    /**
     * @param ScopeConfigInterface $scopeConfig Store configuration reader
     */
    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig,
    ) {
    }

    /**
     * Returns the minimum number of characters before the first autocomplete request fires.
     *
     * @return int
     */
    public function getMinQueryLength(): int
    {
        return (int) $this->scopeConfig->getValue(
            self::XML_PATH_MIN_QUERY_LENGTH,
            ScopeInterface::SCOPE_STORE
        ) ?: 3;
    }

    /**
     * Returns the debounce delay in milliseconds applied before firing a request.
     *
     * @return int
     */
    public function getDebounceMs(): int
    {
        return (int) $this->scopeConfig->getValue(
            self::XML_PATH_DEBOUNCE_MS,
            ScopeInterface::SCOPE_STORE
        ) ?: 300;
    }

    /**
     * Returns the maximum number of product suggestions rendered in the autocomplete dropdown.
     *
     * @return int
     */
    public function getMaxSuggestions(): int
    {
        return (int) $this->scopeConfig->getValue(
            self::XML_PATH_MAX_SUGGESTIONS,
            ScopeInterface::SCOPE_STORE
        ) ?: 6;
    }
}

3. GraphQL-Autocomplete-Query

Die eigentliche Such-Intelligenz kommt aus einer eigenen GraphQL-Query, die parallel zur Produktsuche auch Kategorie-Vorschläge abfragt. Der products-Query-Typ liefert über search, pageSize und Felder wie sku, name, small_image und price_range alles, was für eine kompakte Produktvorschau nötig ist. Für Live-Search-Autocomplete reicht es, pageSize auf einen kleinen Wert wie 5 oder 6 zu begrenzen, weil die Autocomplete-Liste keine vollständige Trefferliste, sondern nur eine Orientierungshilfe sein soll.

Zusätzlich zur Produktsuche fragt man categoryList mit einem name-Match-Filter ab, um passende Kategorien direkt in den Vorschlägen anzuzeigen, ein Muster, das bei Adobe Live Search und klassischer Elasticsearch-Integration gleichermaßen funktioniert, sofern das Schema categoryList unterstützt. Wichtig ist, dass die Query so schlank wie möglich bleibt: Jedes zusätzliche Feld erhöht die Antwortzeit, und bei einer Autocomplete-Komponente zählt jede Millisekunde, weil der Nutzer währenddessen weitertippt.

Bevor die Query überhaupt abgeschickt wird, muss das Debounce-Handling im Frontend greifen. Ohne Debounce würde jede Tasteneingabe einen eigenen GraphQL-Request auslösen, bei einem zehn Zeichen langen Suchbegriff also zehn parallele Anfragen, von denen neun überflüssig sind. Die Kombination aus Debounce im Alpine-Code und einer schlanken Query ist die Grundlage für eine performante Live-Search-Autocomplete im Hyvä-Header.


query LiveSearchAutocomplete($search: String!, $pageSize: Int!) {
  products(search: $search, pageSize: $pageSize) {
    total_count
    items {
      sku
      name
      small_image {
        url
      }
      price_range {
        minimum_price {
          final_price {
            value
            currency
          }
        }
      }
    }
  }
  categoryList(filters: { name: { match: $search } }) {
    uid
    name
    url_path
  }
}

4. Alpine.js-Autocomplete-Komponente

Das Herzstück der Komponente ist eine Alpine.js-Funktion mit klar definiertem State: query für den aktuellen Suchbegriff, results für die Produktliste, loading für den Ladezustand und activeIndex für die per Tastatur markierte Position. Dieser State lebt vollständig im x-data-Objekt und wird nirgends global im window-Objekt gehalten, was mehrere unabhängige Suchfelder auf einer Seite ohne Konflikte ermöglicht.

Der kritische Teil ist die Request-Cancellation: Tippt ein Nutzer schnell weiter, während eine GraphQL-Antwort noch unterwegs ist, darf die ältere Antwort nicht die neuere überschreiben. Ein AbortController pro Komponente löst dieses Problem elegant, vor jedem neuen fetch()-Aufruf wird der vorherige Controller abgebrochen, sodass nur die zuletzt gestartete Anfrage tatsächlich results setzt. Ohne dieses Pattern flackert die Live-Search-Autocomplete-Liste bei schnellem Tippen zwischen alten und neuen Treffern.

Fehlerbehandlung gehört ebenfalls zum Komponenten-Code: Ein abgebrochener Request wirft einen AbortError, der explizit ignoriert werden muss, während echte Netzwerkfehler geloggt und dem Nutzer als klar kommunizierter Leerzustand angezeigt werden. Der folgende Code zeigt die vollständige Komponente inklusive Tastatur-Handling für Pfeiltasten und Enter.


// Alpine.js component: Live Search autocomplete with request cancellation
function hyvaLiveSearchAutocomplete(config) {
  return {
    query: '',
    results: [],
    categories: [],
    loading: false,
    open: false,
    activeIndex: -1,
    minChars: config.minChars,
    debounceMs: config.debounceMs,
    maxSuggestions: config.maxSuggestions,
    abortController: null,

    async fetchSuggestions() {
      if (this.query.trim().length < this.minChars) {
        this.results = [];
        this.open = false;
        return;
      }

      // Cancel the previous in-flight request before starting a new one
      if (this.abortController) {
        this.abortController.abort();
      }
      this.abortController = new AbortController();

      this.loading = true;
      this.activeIndex = -1;

      try {
        const response = await fetch('/graphql', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'Store': hyva.storeCode },
          signal: this.abortController.signal,
          body: JSON.stringify({
            query: window.liveSearchAutocompleteQuery,
            variables: { search: this.query, pageSize: this.maxSuggestions }
          })
        });

        const payload = await response.json();
        this.results = payload.data.products.items;
        this.categories = payload.data.categoryList;
        this.open = true;
      } catch (error) {
        // Ignore aborts caused by newer keystrokes, log real failures
        if (error.name !== 'AbortError') {
          console.error('Live Search autocomplete request failed', error);
        }
      } finally {
        this.loading = false;
      }
    },

    moveActive(step) {
      if (!this.open || this.results.length === 0) return;
      const count = this.results.length;
      this.activeIndex = (this.activeIndex + step + count) % count;
    },

    selectActive() {
      if (this.activeIndex > -1) {
        this.selectResult(this.results[this.activeIndex]);
      }
    },

    selectResult(item) {
      window.location.href = item.url ?? ('/catalog/product/view/sku/' + item.sku);
    },

    closeSuggestions() {
      this.open = false;
      this.activeIndex = -1;
    }
  };
}

5. Tastatur-Navigation und Barrierefreiheit

Eine Live-Search-Autocomplete-Komponente ohne Tastatur-Bedienung schließt einen erheblichen Teil der Nutzer aus, sowohl Screenreader-Nutzer als auch alle, die aus Gewohnheit mit der Tastatur navigieren. Die ARIA-Rolle combobox am Eingabefeld in Kombination mit role="listbox" auf dem Ergebnis-Container und role="option" auf jedem Eintrag ist das vom WAI-ARIA-Authoring-Pattern vorgegebene Grundgerüst für genau diesen Anwendungsfall.

Das Attribut aria-activedescendant zeigt Screenreadern, welcher Eintrag aktuell markiert ist, ohne den Fokus tatsächlich zu verschieben, der Fokus bleibt im Eingabefeld, während activeIndex per Pfeiltaste hoch- und runtergezählt wird. Escape schließt die Vorschlagsliste und setzt activeIndex zurück auf -1, Enter übernimmt den aktuell markierten Vorschlag oder löst, falls kein Eintrag markiert ist, die reguläre Volltextsuche aus.

Wichtig ist außerdem die aria-expanded-Bindung am Eingabefeld, die dynamisch zwischen true und false wechselt, sowie ein sr-only-Label, das den Zweck des Feldes auch ohne sichtbaren Text kommuniziert. Diese Details entscheiden bei einer Autocomplete im Hyvä-Header-Implementierung häufig über Erfolg oder Misserfolg im Rahmen einer Barrierefreiheitsprüfung nach WCAG 2.1 AA.

6. Rendering der Vorschläge

Jeder Vorschlag in der Liste zeigt üblicherweise ein kleines Produktbild, den Namen und den Preis, kompakt genug, um mehrere Treffer gleichzeitig darzustellen, aber informativ genug, um eine Kaufentscheidung vorzubereiten. Das small_image-Feld aus der GraphQL-Antwort liefert die Bild-URL, price_range.minimum_price.final_price liefert Wert und Währung, sodass der Preis ohne zusätzliche Formatierungslogik im Template direkt mit x-text ausgegeben werden kann.

Neben Produkten gehören Kategorie- und CMS-Vorschläge zu einer vollständigen Live-Search-Autocomplete im Hyvä-Header. Kategorien erscheinen typischerweise in einer eigenen Sektion oberhalb oder unterhalb der Produktliste, mit einem Icon oder Label, das die Ergebnisart kennzeichnet. Das verhindert, dass Nutzer eine Kategorie mit einem Produkt verwechseln.

Für den Fall, dass keine Treffer gefunden werden, braucht die Komponente einen expliziten Empty-State, einen kurzen Hinweistext statt einer leeren, aber sichtbaren Box. Ein guter Empty-State schlägt idealerweise auch alternative Aktionen vor, etwa einen Link zur vollständigen Suchergebnisseite oder zu beliebten Kategorien, damit der Nutzer bei einer erfolglosen Suche nicht ins Leere läuft.


{
  "data": {
    "products": {
      "total_count": 42,
      "items": [
        {
          "sku": "WT08-XS-Blue",
          "name": "Zoltan Wool Sweater",
          "small_image": { "url": "https://mironsoft.de/media/catalog/product/cache/wt08.jpg" },
          "price_range": {
            "minimum_price": {
              "final_price": { "value": 59.9, "currency": "EUR" }
            }
          }
        }
      ]
    },
    "categoryList": [
      { "uid": "Mg==", "name": "Pullover", "url_path": "damen/pullover" }
    ]
  }
}

7. CSP-Konformität

Hyvä setzt aus gutem Grund auf eine strikte Content-Security-Policy, die Inline-Scripts nur zulässt, wenn sie explizit über das HyvaCsp-ViewModel registriert werden. Für die Live-Search-Autocomplete-Komponente bedeutet das: Jeder Inline-<script>-Block, der etwa die GraphQL-Query als Konstante bereitstellt oder Alpine-Komponenten registriert, muss unmittelbar von $hyvaCsp->registerInlineScript() gefolgt sein.

Externe Script-Quellen sind für diese Funktion nicht nötig und sollten es auch nicht sein, die komplette Autocomplete-Logik läuft über Alpine.js, das bereits Teil des Hyvä-Themes ist, plus eine schlanke, selbst geschriebene JavaScript-Funktion. Wer stattdessen ein externes Autocomplete-Skript von einem CDN einbindet, unterläuft die CSP-Konfiguration und schafft eine zusätzliche Angriffsfläche sowie eine externe Abhängigkeit, die bei einem Ausfall des CDN die gesamte Kopfzeilen-Suche lahmlegt.

In der Praxis reicht ein einziger Inline-Block im Layout, der die Alpine-Funktion global registriert, gefolgt vom passenden registerInlineScript()-Aufruf. Damit bleibt Live-Search-Autocomplete im Hyvä-Header vollständig CSP-konform, ohne dass zusätzliche Nonce- oder Hash-Ausnahmen in der CSP-Konfiguration des Shops nötig werden.

8. Performance

Debounce-Timing ist der wichtigste Stellhebel für die gefühlte Performance von Live-Search-Autocomplete. Ein Wert zwischen 250 und 350 Millisekunden hat sich in der Praxis bewährt: kurz genug, damit sich die Suche noch reaktionsschnell anfühlt, lang genug, um die Zahl unnötiger Requests bei schnellem Tippen spürbar zu reduzieren. Werte unter 150 Millisekunden bringen kaum wahrnehmbaren Geschwindigkeitsgewinn, erzeugen aber deutlich mehr Serverlast.

Genauso wichtig ist eine Mindestzeichenzahl vor dem ersten Request, typischerweise drei Zeichen. Ein einzelner Buchstabe liefert ohnehin zu viele, kaum relevante Treffer und würde bei Shops mit hohem Traffic unnötig viele GraphQL-Anfragen an die Suchengine schicken. Diese Schwelle lässt sich, wie im ViewModel-Beispiel gezeigt, direkt über die Systemkonfiguration steuern, ohne den Code anzufassen.

Für häufig wiederkehrende Suchanfragen lohnt sich eine Cache-Strategie auf HTTP-Ebene: Adobe Live Search cached Suggestion-Antworten serverseitig, bei einer klassischen Elasticsearch-Anbindung kann ein kurzlebiger Full-Page-Cache oder ein Edge-Cache vor dem GraphQL-Endpunkt ähnliche Effekte erzielen. Wichtig ist, dass der Cache-Schlüssel Suchbegriff, Store-View und Kundengruppe berücksichtigt, damit keine falschen Preise oder Sichtbarkeiten in den Vorschlägen von Live-Search-Autocomplete landen.

9. Tracking und Analytics

Jede abgeschlossene Live-Search-Autocomplete-Anfrage ist ein Signal für die Sortiments- und Suchoptimierung und sollte deshalb in den dataLayer gepusht werden. Ein Event wie search_suggestion_shown mit Suchbegriff, Trefferanzahl und Zeitstempel liefert die Datenbasis für spätere Auswertungen, welche Suchbegriffe häufig getippt, aber selten angeklickt werden.

Mindestens ebenso wertvoll ist das No-Result-Tracking: Sucht ein Nutzer nach einem Begriff, für den Live-Search-Autocomplete keine Produkte findet, ist das ein direkter Hinweis auf eine Sortimentslücke, ein fehlendes Synonym in der Suchengine oder einen Tippfehler, der sich durch eine Rechtschreibkorrektur beheben ließe. Ein Event wie search_no_results, gefüllt mit dem exakten Suchbegriff, macht diese Fälle im Analytics-Tool auswertbar.

Für die technische Umsetzung reicht ein window.dataLayer.push() direkt in der fetchSuggestions()-Methode der Alpine-Komponente, wieder eingebettet in einen registrierten Inline-Script-Block. Wichtig: Das Tracking darf niemals den eigentlichen Request blockieren oder verzögern, es läuft als Fire-and-Forget-Aufruf parallel zur Darstellung der Vorschläge.

Die folgenden Muster fassen die wichtigsten Unterschiede zwischen einer naiv umgesetzten und einer robusten Live-Search-Autocomplete im Hyvä-Header zusammen.

Aufgabe Naiver Ansatz Empfohlenes Hyvä-Pattern Nutzen
Anfrage bei Eingabe Kein Debounce, ein Request pro Tastendruck Debounced fetch (250-350 ms) Weniger Serverlast, flüssigeres Tippen
Antworten bei schnellem Tippen Keine Request-Cancellation AbortController pro Komponente Immer aktuelle, konsistente Vorschläge
Inline-Script im Header Ungeprüfter <script>-Block $hyvaCsp->registerInlineScript() CSP-konform, kein Blockieren durch den Browser
Bedienung der Vorschlagsliste Nur mit der Maus erreichbar aria-activedescendant + Pfeiltasten Barrierefreiheit nach WCAG 2.1 AA
Auswertung von Fehlsuchen Kein No-Result-Tracking dataLayer-Event search_no_results Datenbasis für Sortimentsoptimierung

In der Summe entscheidet die Kombination dieser Muster darüber, ob Live-Search-Autocomplete im Hyvä-Header als spürbare Verbesserung wahrgenommen wird oder als zusätzliche Fehlerquelle im Kopfbereich des Shops.

Mironsoft

Hyvä-Suche, GraphQL-Integration und Frontend-Performance

Live-Search-Autocomplete zuverlässig in euren Hyvä-Header integrieren?

Wir implementieren GraphQL-basierte Autocomplete-Komponenten, die barrierefrei, CSP-konform und performant sind, von der Suggestion-Query bis zur fertigen Alpine.js-Komponente im Header.

GraphQL-Integration

Suggestion-Queries für Produkte, Kategorien und CMS-Inhalte konzipieren und anbinden

Alpine-Komponenten

Debounce, AbortController und State-Handling für den Hyvä-Header umsetzen

Barrierefreiheit-Audit

Tastatur-Navigation und ARIA-Struktur nach WCAG 2.1 AA prüfen und nachrüsten

10. Zusammenfassung

Eine saubere Live-Search-Autocomplete-Integration im Hyvä-Header löst ein konkretes Problem: Die Standard-Quick-Search zeigt während der Eingabe keine Vorschläge, obwohl die Suchengine im Hintergrund längst relevanzsortierte Ergebnisse liefern könnte. Eine schlanke GraphQL-Query, eine Alpine.js-Komponente mit Debounce und AbortController sowie ein ARIA-konformes Markup verwandeln das einfache Eingabefeld in eine vollwertige, barrierefreie Suche direkt im Header.

Der größte Hebel liegt darin, alle Bausteine konsequent zusammenzuführen: CSP-konforme Script-Registrierung, ein durchdachtes Debounce-Timing, ein aussagekräftiger Empty-State und Tracking-Events für Treffer und Fehlsuchen. Wer diese Punkte von Anfang an mitdenkt, bekommt eine Autocomplete im Hyvä-Header, die nicht nur schnell wirkt, sondern auch messbar zur Sortiments- und Suchoptimierung beiträgt.

Live-Search-Autocomplete im Hyvä-Header, das Wichtigste auf einen Blick

GraphQL-Query

Schlanke Suggestion-Query mit products und categoryList, begrenzt auf wenige Felder und eine kleine pageSize.

Alpine-State & Cancellation

Debounce, query/results/loading/activeIndex und ein AbortController pro Komponente verhindern veraltete Vorschläge.

Barrierefreiheit & CSP

role="combobox", aria-activedescendant und Pfeiltasten-Navigation, jeder Inline-Script-Block mit registerInlineScript().

Performance & Tracking

Debounce 250-350 ms, Mindestzeichenzahl 3, Cache-Strategie pro Store-View, dataLayer-Events für Treffer und Fehlsuchen.

11. FAQ: Live-Search-Autocomplete im Hyvä-Header

1Was ist Live-Search-Autocomplete im Hyvä-Kontext?
Eine Erweiterung der Kopfzeilen-Suche, die während der Eingabe per GraphQL Produkt-, Kategorie- und CMS-Vorschläge nachlädt, statt erst nach Enter zur Ergebnisliste zu springen.
2Unterschied zur Standard-Hyvä-Suche?
Die Standard-Box zeigt keine Live-Vorschläge. Autocomplete im Hyvä-Header ergänzt GraphQL-Query, Alpine-State und ARIA-Attribute für live erscheinende, tastaturbedienbare Vorschläge.
3Wo liegt die header-search-Vorlage?
Standardmäßig templates/header/search.phtml im Magento_Search-Modul. Im eigenen Theme überschreiben und über Layout-XML einbinden, ohne die Child-Block-Reihenfolge zu ändern.
4Wie sieht die GraphQL-Suggestion-Query aus?
products mit search und pageSize kombiniert mit categoryList und name-Match-Filter, begrenzt auf wenige Felder für schnelle Antwortzeiten.
5Warum ein AbortController?
Verhindert, dass eine ältere Antwort eine neuere überschreibt, wenn schnell weitergetippt wird. Die vorherige Anfrage wird abgebrochen, bevor eine neue startet.
6Wie funktioniert die Tastatur-Navigation?
Fokus bleibt im Eingabefeld, activeIndex zählt per Pfeiltaste, aria-activedescendant zeigt die Markierung, Escape schließt, Enter übernimmt den Vorschlag.
7Wie bleibt sie CSP-konform?
Jeder Inline-Script-Block direkt gefolgt von $hyvaCsp->registerInlineScript(). Keine externen Script-Quellen nötig, da Alpine.js bereits Teil des Themes ist.
8Welches Debounce-Timing empfiehlt sich?
250 bis 350 Millisekunden, kombiniert mit einer Mindestzeichenzahl von drei vor dem ersten Request.
9Wie trackt man Sucherereignisse?
window.dataLayer.push() in fetchSuggestions(), etwa search_suggestion_shown mit Trefferanzahl und ein separates search_no_results-Event bei leeren Ergebnissen.
10Was zeigt die Liste bei keinem Treffer?
Einen expliziten Empty-State mit Hinweistext, idealerweise ergänzt um einen Link zur vollständigen Suche oder zu beliebten Kategorien.