← Blog

Variantenauswahl-UX in React: ausverkaufte Größen, fehlende Kombinationen und Wiederverfügbarkeit

Farben und Größen wirken einfach, bis der Bestand an jeder Kombination hängt. Eine React-Variantenauswahl für ausverkaufte Größen, fehlende Kombinationen, Fotos, Preise und die URL.

Preet Suthar · · 5 Min. Lesezeit

  • React
  • E-commerce
  • UX
  • Accessibility

Ein Shirt in drei Farben und fünf Größen sind für dein Lager fünfzehn Produkte. Der Bestand hängt an jeder Kombination, nicht an „Oat“ oder „Large“. Die meisten Variantenauswahlen ignorieren das. Sie rendern Farben und Größen als zwei unabhängige Listen, und die Kundin erfährt erst, dass es Oat nicht in XL gibt, wenn „In den Warenkorb“ fehlschlägt.

So baust du in React eine Variantenauswahl, die beim Bestand die Wahrheit sagt: ausverkaufte Größen, Kombinationen, die es nicht gibt, E-Mails bei Wiederverfügbarkeit sowie Fotos und Preise, die der Auswahl folgen.

Varianten modellieren, Optionen ableiten

Fang bei den Varianten an, denn die liefert deine Commerce-API zurück, und auf sie verweist eine Bestellung:

type Variant = {
  id: string
  options: Record<string, string>
  available: boolean
  quantity?: number
  price: Money
  imageId?: string
}

type Product = {
  options: { name: string; values: string[] }[]
  variants: Variant[]
}

Jeder Wert auf dem Bildschirm bekommt einen von drei Zuständen, abgeleitet aus den Varianten und dem Rest der aktuellen Auswahl:

  • Verfügbar: Irgendeine Variante mit diesem Wert und den übrigen gewählten Optionen ist auf Lager.
  • Ausverkauft: Die Kombination existiert, ist aber gerade nicht auf Lager.
  • Nicht erhältlich: Die Kombination wird überhaupt nicht hergestellt.
type ValueState = "available" | "sold-out" | "unavailable"

function getValueState(
  product: Product,
  selection: Record<string, string>,
  name: string,
  value: string
): ValueState {
  const wanted = { ...selection, [name]: value }
  const matches = product.variants.filter((variant) =>
    Object.entries(wanted).every(
      ([key, chosen]) => variant.options[key] === chosen
    )
  )
  if (matches.length === 0) return "unavailable"
  return matches.some((variant) => variant.available) ? "available" : "sold-out"
}

So bekommst du das Verhalten, das Kunden erwarten, ganz ohne Sonderfälle. Eine Farbe bleibt verfügbar, solange irgendeine ihrer Größen auf Lager ist. Eine Größe wird nur bei den Farben durchgestrichen, in denen sie ausverkauft ist. Wähl eine andere Farbe, und die Größen aktualisieren sich.

Es ist eine reine Funktion, also läuft sie auch auf dem Server. Rendere den ersten Paint mit den richtigen Zuständen, statt kurz jede Größe als verfügbar anzuzeigen und dann einige durchzustreichen.

Swatches und Buttons, niemals ein Dropdown

Ein <select> versteckt genau das, was die Leute prüfen wollen: ob ihre Größe auf Lager ist. Zeig Farben als Swatches und Größen als Buttons, damit das ganze Bild auf einmal sichtbar ist.

Semantisch ist jede Option eine Radio-Gruppe. Nutz echte Radio-Inputs oder role="radiogroup" mit Roving Focus, damit die Pfeiltasten zwischen den Werten wechseln und die Gruppe einen Namen hat. Gib den Zustand auch an assistive Technologien weiter, denn eine Durchstreichung ist für Screenreader unsichtbar und für alle anderen leicht zu übersehen:

<label className="swatch" data-state={state}>
  <input
    type="radio"
    name="size"
    value={size}
    checked={selection.size === size}
    onChange={() => select("size", size)}
    disabled={state === "unavailable"}
    aria-describedby={state === "sold-out" ? "size-sold-out" : undefined}
  />
  {size}
</label>

Zeig über den Swatches den gewählten Wert als Text („Farbe: Oat“). Beim Hovern zeigst du dort den Namen des gehoverten Werts an, damit niemand eine Farbe aus einem 32px-Kreis erraten muss.

Ausverkauft sollte trotzdem auswählbar sein

Der typische Fehler ist, ausverkaufte Größen zu deaktivieren. Das versteckt die nützlichste Information, nämlich welche Größen es gibt, und es nimmt den Weg zu einer E-Mail bei Wiederverfügbarkeit.

Lass ausverkaufte Werte auswählbar. Ist die gewählte Variante ausverkauft, ersetze „In den Warenkorb“ durch ein Formular „Benachrichtige mich, wenn es wieder da ist“, das die Varianten-ID speichert, nicht die Produkt-ID, damit es in der E-Mail um Oat in Large geht und nicht um „dieses Shirt“.

Nur Kombinationen, die es nicht gibt, sollten deaktiviert sein, und sie sollten im zugänglichen Namen sagen, warum: „XL, nicht in Oat erhältlich“.

Größe nicht vorauswählen

Die erste Größe vorauszuwählen ist der Grund, warum Shops so viel Größe S verschicken. Wähl die Farbe vor, weil das Foto sie ohnehin zeigt, und lass die Größe leer.

Wenn jemand ohne Größe auf „In den Warenkorb“ drückt, scheitere nicht stillschweigend und öffne keinen Alert. Markiere die Größengruppe mit „Wähle eine Größe“, setz den Fokus darauf und lass sie kurz wackeln, was bei reduzierter Bewegung abgeschaltet wird.

function addToBag() {
  const missing = product.options.find((option) => !selection[option.name])
  if (missing) {
    setError(missing.name, `Select a ${missing.name.toLowerCase()}`)
    groupRefs.current[missing.name]?.focus()
    return
  }
  add(findVariant(product, selection))
}

Wechsel zu einer Kombination, die es nicht gibt

Die Kundin hat Large ausgewählt und wählt dann eine Farbe, die es nur in Small und Medium gibt. Du hast zwei Möglichkeiten: die Farbe blockieren oder sie übernehmen und die Größe zurücksetzen. Die Größe zurückzusetzen ist besser, denn die Farbe ist meist die größere Entscheidung. Aber sag es. Eine höfliche Live-Region mit „Large gibt es nicht in Rust. Wähle eine Größe.“ verhindert, dass Screenreader-Nutzer das Falsche in den Warenkorb legen, und die sichtbare Größengruppe zeigt allen anderen die leere Auswahl.

Fotos und Preis folgen der Auswahl

Wenn sich die Farbe ändert, sollte die Galerie zum Foto dieser Farbe wechseln. Blende an Ort und Stelle über, statt an jedem Bild dazwischen vorbeizuschieben, und warte, bis das neue Bild dekodiert ist, damit der Rahmen nie leer aufblitzt. Lass die Kundin danach weiter durch die anderen Fotos blättern. Die Variante bestimmt, wo die Galerie startet, nicht, wo sie festsitzt.

Auch Preise können sich je Variante unterscheiden. Vor einer vollständigen Auswahl zeigst du eine Spanne, „Ab $28“. Sobald eine Variante gewählt ist, zeig ihren Preis, bei einem Sale den durchgestrichenen Vergleichspreis und ihren Bestand in Worten: „Nur noch 3 übrig“, „Vorbestellung, Versand ab 12. Nov.“ oder „Nachbestellt“. Nutz Tabellenziffern für den Preis, damit die Zeile nicht springt, wenn sich die Ziffern ändern.

Die Auswahl gehört in die URL

?color=Oat&size=L macht eine Variante teilbar, übersteht ein Neuladen und erlaubt Anzeigen und E-Mails, genau auf das Produkt zu verlinken, das jemand gesehen hat. Lies den Wert sorgfältig zurück und verwirf alles, was das Produkt nicht hat:

function selectionFromSearchParams(product: Product, params: URLSearchParams) {
  const selection: Record<string, string> = {}
  for (const { name, values } of product.options) {
    const value = params.get(name.toLowerCase())
    if (value && values.includes(value)) selection[name] = value
  }
  return selection
}

Lies in Next.js searchParams in der Page, damit der Server die richtige Variante rendert, und aktualisiere die URL vom Client aus, wenn sich die Auswahl ändert. Entscheide, ob jede Änderung einen neuen History-Eintrag anlegt oder den aktuellen ersetzt, und bleib dabei konsistent.

Eine Checkliste

  • Optionszustände aus den Varianten ableiten: verfügbar, ausverkauft, nicht erhältlich.
  • Swatches und Buttons in Radio-Gruppen, mit dem Zustand im zugänglichen Namen.
  • Ausverkaufte Werte bleiben auswählbar und bieten pro Variante eine E-Mail bei Wiederverfügbarkeit an.
  • Die Größe wird nie vorausgewählt, und eine fehlende Größe wird markiert und fokussiert.
  • Ein Farbwechsel, der die Größe ungültig macht, setzt sie zurück und sagt, warum.
  • Galerie und Preis folgen der Variante, ohne die Galerie festzusetzen.
  • Die Auswahl lebt in der URL.

Die passenden HextaUI-Blöcke

Diese Regeln habe ich in den Variant Picker-Block in HextaUI Pro eingebaut. Er leitet jede Option aus den übergebenen Varianten ab, lässt ausverkaufte Werte mit einem NotifyMe-Formular auswählbar, wählt nie Größen vor und markiert eine fehlende mit „Wähle eine Größe“. Seine Helfer, findVariant, getOptionStates, getInitialSelection, getPriceRange und das Search-Param-Paar, funktionieren auch eigenständig, ebenso auf dem Server, und fromShopify bildet ein Produkt aus der Storefront API ab.

Er passt zu Product Gallery, die per Crossfade zum Foto einer Variante wechselt, wenn du activeId übergibst, und zu Price, das jede Währung formatiert und nur die Ziffern animiert, die sich geändert haben. Alle drei haben Live-Vorschauen, die du vor dem Kauf ausprobieren kannst.

Wenn du als Nächstes den Warenkorb baust, gilt dort dieselbe Sorgfalt: Warenkorb-UX in React.

Häufige Fragen

Sollten ausverkaufte Größen deaktiviert werden? Nein. Lass sie auswählbar und biete eine E-Mail bei Wiederverfügbarkeit an. Deaktiviere nur Kombinationen, die es überhaupt nicht gibt.

Sollte ich eine Größe vorauswählen? Nein. Wähl die Farbe vor, wenn du willst, und validiere die Größe, wenn jemand in den Warenkorb legt.

Swatches oder Dropdown für Varianten? Swatches und Buttons. Ein Dropdown versteckt den Bestand, und genau diese Information suchen die Leute.

Wie halte ich eine Variante in der URL? Nutz einen Search-Param pro Option, lies ihn beim ersten Rendern auf dem Server und ignoriere Werte, die das Produkt nicht hat.