Use Cases (Aktualisiert: 2.6.2026)

Schnelle Bildergalerie mit Claude Code bauen

Responsive Bildergalerie mit Claude Code: React-Code, srcset, Lightbox, typische Fehler und Prüfungen.

Schnelle Bildergalerie mit Claude Code bauen

Eine Bildergalerie ist mehr als ein Layout

Eine Bildergalerie mit Claude Code zu bauen heißt nicht nur, ein paar Bilder hübsch anzuordnen. Die Galerie kann ein Produkt verkaufen, einen Case Study belegen, Workshop-Material zeigen oder technische Screenshots vergleichbar machen. Wenn der Prompt nur “baue eine schöne Masonry-Galerie” sagt, entsteht oft eine Demo, aber keine belastbare Produktionslösung. Die kritischen Punkte sind Bildgewicht, alt-Text, Layout Shift, mobile Breite, Tastaturbedienung, fehlerhafte CMS-Daten und der nächste Schritt nach dem Bild.

Dieser Guide behandelt die Galerie als veröffentlichbare UI. Wir geben Claude Code klare Grenzen, bauen eine typisierte React-Komponente, nutzen srcset, sizes, Lazy Loading und Lightbox, und prüfen typische Fehler vor dem Rollout. Ergänzend passen Claude Code Bildverarbeitung, Performance-Optimierung und Accessibility-Implementierung. Als externe Referenzen nutze die Claude Code Dokumentation, MDN zu responsive images, MDN zu Lazy loading und WCAG 2.2.

Mein praktischer Startpunkt ist der Datenvertrag. Jede Bildzeile braucht id, Kategorie, Breite, Höhe und einen sinnvollen Alternativtext. Danach kann das Team immer noch entscheiden, ob rohe img-Tags, Next.js Image, Astro Assets oder ein CDN genutzt werden.

Claude Code mit Grenzen beauftragen

Dieser Prompt ist bewusst konkret. Er fordert lauffähigen Code, Fehlerzustände und eine Review-Antwort.

Implementiere eine React-Bildergalerie.
Ziel ist eine schnelle UI für Artikel, Case Studies, Produkt-Screenshots und Workshop-Fotos.

Bedingungen:
- Bestehendes Routing und Design-System-Konventionen nicht beschädigen.
- Einen Bildtyp mit id, src, alt, width, height und category als Pflichtfelder definieren.
- CSS Grid für das responsive Layout verwenden.
- srcset, sizes, loading und fetchPriority bewusst einsetzen.
- Per Klick eine Lightbox öffnen und mit Escape schließen.
- Leere Arrays, Bildfehler, lange alt-Texte und mobile Breite behandeln.
- Danach geänderte Dateien, Tests und Restrisiken erklären.

Gib kopierbares React/TypeScript und CSS zurück, keinen Pseudocode.

Damit verschiebt sich die Ausgabe von “schöne Vorschau” zu “kleiner prüfbarer Patch”. width und height reduzieren Layout Shift. Pflicht-alt verhindert leere Beschreibungen. Die Risikofrage zwingt Claude Code zu einer zweiten Review-Perspektive.

Empfohlene Struktur

Vor dem Code hilft ein einfaches Ablaufbild. Claude Code trennt Verantwortlichkeiten besser, wenn Daten, UI und Prüfung sichtbar sind.

flowchart LR
  A["Originalbilder"] --> B["Größenvarianten"]
  B --> C["GalleryImage Array"]
  C --> D["Kategorie-Filter"]
  D --> E["CSS Grid Karten"]
  E --> F["Lightbox"]
  E --> G["Lighthouse und manuelle Prüfung"]
EntscheidungSicherer StartNeu bewerten wenn
LayoutCSS GridBildhöhen stark variieren
Lazy LoadingUnterhalb des ersten ViewportsDas Hauptbild zu spät erscheint
VariantenEtwa 480/960/1440pxViele große Displays im Analytics auftauchen
LightboxMinimale zugängliche VersionDie Galerie Kaufentscheidungen beeinflusst

Eine Masonry-Library ist nicht automatisch nötig. Viele Seiten brauchen stabile Karten, schnelle Thumbnails und eine klare Vergrößerung. Weniger Abhängigkeiten machen den von Claude Code erzeugten Diff außerdem leichter prüfbar.

Kopierbare React-Implementierung

Die Komponente ist bewusst framework-arm. Sie funktioniert in Vite, kann in eine Next.js Client Component wandern und später an vorhandene Bildabstraktionen angepasst werden.

import { useEffect, useMemo, useState } from "react";
import "./image-gallery.css";

export type GalleryImage = {
  id: string;
  src: string;
  alt: string;
  width: number;
  height: number;
  category: string;
  sources?: Array<{ width: number; src: string }>;
};

function buildSrcSet(image: GalleryImage) {
  if (!image.sources?.length) return undefined;

  return [...image.sources]
    .sort((a, b) => a.width - b.width)
    .map((source) => `${source.src} ${source.width}w`)
    .join(", ");
}

export function ImageGallery({ images }: { images: GalleryImage[] }) {
  const [category, setCategory] = useState("all");
  const [activeId, setActiveId] = useState<string | null>(null);
  const [brokenIds, setBrokenIds] = useState<Set<string>>(() => new Set());

  const categories = useMemo(() => {
    return ["all", ...Array.from(new Set(images.map((image) => image.category)))];
  }, [images]);

  const visibleImages = useMemo(() => {
    if (category === "all") return images;
    return images.filter((image) => image.category === category);
  }, [category, images]);

  const activeImage = visibleImages.find((image) => image.id === activeId);

  useEffect(() => {
    if (!activeImage) return;

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") setActiveId(null);
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [activeImage]);

  function markBroken(id: string) {
    setBrokenIds((current) => new Set(current).add(id));
  }

  if (images.length === 0) {
    return <p className="gallery-empty">No images are available yet.</p>;
  }

  return (
    <section className="gallery" aria-label="Image gallery">
      <div className="gallery-toolbar" aria-label="Filter images by category">
        {categories.map((item) => (
          <button
            className={item === category ? "is-active" : ""}
            key={item}
            onClick={() => setCategory(item)}
            type="button"
          >
            {item === "all" ? "All" : item}
          </button>
        ))}
      </div>

      <div className="gallery-grid">
        {visibleImages.map((image, index) => {
          const isBroken = brokenIds.has(image.id);

          return (
            <button
              className="gallery-card"
              key={image.id}
              onClick={() => setActiveId(image.id)}
              type="button"
            >
              {isBroken ? (
                <span className="gallery-fallback">Image unavailable</span>
              ) : (
                <img
                  alt={image.alt}
                  width={image.width}
                  height={image.height}
                  src={image.src}
                  srcSet={buildSrcSet(image)}
                  sizes="(min-width: 960px) 33vw, (min-width: 640px) 50vw, 100vw"
                  loading={index < 2 ? "eager" : "lazy"}
                  fetchPriority={index === 0 ? "high" : "auto"}
                  style={{ aspectRatio: `${image.width} / ${image.height}` }}
                  onError={() => markBroken(image.id)}
                />
              )}
              <span>{image.alt}</span>
            </button>
          );
        })}
      </div>

      {activeImage && (
        <div
          className="gallery-lightbox"
          role="dialog"
          aria-modal="true"
          aria-label={activeImage.alt}
          tabIndex={-1}
          onClick={() => setActiveId(null)}
        >
          <button className="gallery-close" onClick={() => setActiveId(null)} type="button">
            Close
          </button>
          <img
            alt={activeImage.alt}
            width={activeImage.width}
            height={activeImage.height}
            src={activeImage.src}
            onClick={(event) => event.stopPropagation()}
          />
        </div>
      )}
    </section>
  );
}
.gallery {
  display: grid;
  gap: 1rem;
}

.gallery-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.gallery-toolbar button,
.gallery-card,
.gallery-close {
  border: 1px solid #d4d4d8;
  background: #ffffff;
  color: #18181b;
  cursor: pointer;
}

.gallery-toolbar button {
  border-radius: 999px;
  padding: 0.45rem 0.8rem;
}

.gallery-toolbar .is-active {
  background: #18181b;
  color: #ffffff;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
}

.gallery-card {
  display: grid;
  gap: 0.5rem;
  padding: 0;
  overflow: hidden;
  border-radius: 8px;
  text-align: left;
}

.gallery-card img {
  width: 100%;
  object-fit: cover;
  background: #f4f4f5;
}

.gallery-fallback {
  display: grid;
  min-height: 180px;
  place-items: center;
  background: #f4f4f5;
  color: #71717a;
}

.gallery-card span {
  padding: 0 0.75rem 0.75rem;
  font-size: 0.875rem;
}

.gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 2rem;
  background: rgb(0 0 0 / 0.86);
}

.gallery-lightbox img {
  max-width: min(100%, 1100px);
  max-height: 82vh;
  object-fit: contain;
}

.gallery-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
}

.gallery-empty {
  color: #71717a;
}

In einem echten Projekt kann das rohe img durch die Standard-Bildkomponente ersetzt werden. Wichtig bleibt der Datenvertrag mit alt, width, height und sizes.

Datenmodell und Use Cases

Halte die Daten außerhalb der Komponente. Auch CMS-Daten sollten vor dem Rendern normalisiert werden.

import type { GalleryImage } from "./ImageGallery";

export const galleryImages: GalleryImage[] = [
  {
    id: "case-study-dashboard",
    src: "/images/gallery/dashboard-960.webp",
    alt: "Analytics dashboard after Claude Code refactoring",
    width: 960,
    height: 640,
    category: "Case study",
    sources: [
      { width: 480, src: "/images/gallery/dashboard-480.webp" },
      { width: 960, src: "/images/gallery/dashboard-960.webp" },
      { width: 1440, src: "/images/gallery/dashboard-1440.webp" },
    ],
  },
  {
    id: "workshop-room",
    src: "/images/gallery/workshop-960.webp",
    alt: "Team workshop board with Claude Code review checklist",
    width: 960,
    height: 720,
    category: "Training",
  },
  {
    id: "product-shot",
    src: "/images/gallery/template-pack-960.webp",
    alt: "Claude Code template pack product preview",
    width: 960,
    height: 540,
    category: "Product",
  },
];

Use Case eins ist ein Portfolio oder eine Case-Study-Seite. Bilder sollen Ergebnisse vergleichbar machen und dann zu Artikel, Kontakt oder Beratung führen.

Use Case zwei ist Ecommerce oder ein digitales Produkt. Screenshots, Nutzungsszenen und Vergleichsbilder nehmen Unsicherheit. Alle hochauflösenden Bilder vor dem CTA zu laden, ist dagegen teuer.

Use Case drei sind Schulung, Event und internes Wissen. Whiteboards, Schrittbilder, Vorher/Nachher und Fehlerscreens werden wiederverwendbares Material. Intern müssen Kundennamen, E-Mails und Secrets geprüft werden.

Use Case vier sind technische Artikel. Bei mehreren Codebeispielen helfen Diagramme und Prüfscreenshots, damit Leser den Kontext behalten.

Typische Fehler

Der häufigste Fehler ist Lazy Loading für das zuerst sichtbare Bild. Dieses Bild kann LCP beeinflussen, daher sind eager und fetchPriority="high" für das erste Element oft sinnvoll. Der Gegenfehler ist, alles sofort zu laden.

Der zweite Fehler ist das Weglassen von width und height. Karten ändern dann beim Laden ihre Höhe und die Seite wirkt instabil. Bitte Claude Code um eine explizite CLS-Prüfung.

Der dritte Fehler ist SEO-Stuffing im alt-Text. Alt soll die Bildaussage ersetzen, wenn das Bild nicht gesehen werden kann.

Der vierte Fehler ist eine Maus-only-Lightbox. Ein benannter Schließen-Button, Escape, sichtbarer Fokus und mobile Nutzbarkeit gehören zum Minimum. Für striktes Focus Trapping sind Radix UI oder React Aria oft sicherer.

Der fünfte Fehler sind fehlende Betriebsregeln. Ein 6-MB-PNG aus dem CMS kann die Seite ruinieren. Maximale Größe, Formate, Namensregeln und Review-Punkte gehören in CLAUDE.md.

Prüfung vor der Veröffentlichung

Prüfe Filter, Lightbox, Tastatur, leere Daten, kaputte Bilder und 375px Breite. Mit Playwright reicht zum Start ein kleiner Test:

import { expect, test } from "@playwright/test";

test("image gallery filters and opens a lightbox", async ({ page }) => {
  await page.goto("/gallery");

  await expect(page.getByRole("region", { name: "Image gallery" })).toBeVisible();
  await page.getByRole("button", { name: "Training" }).click();
  await expect(page.getByRole("button", { name: /workshop/i })).toBeVisible();

  await page.getByRole("button", { name: /workshop/i }).click();
  await expect(page.getByRole("dialog")).toBeVisible();

  await page.keyboard.press("Escape");
  await expect(page.getByRole("dialog")).toBeHidden();
});

Die Review-Frage sollte konkret sein: initiale Bildrequests, Abgleich von srcset und sizes, Qualität des Alt-Texts, Button- oder Link-Rollen, mobile Overflows, defekte CMS-Daten und private Informationen in Screenshots.

CTA und praktisches Ergebnis

Eine Galerie sollte einen Geschäftspfad unterstützen. Case-Bilder führen zum Case, Produktbilder zu Kaufdetails, Workshop-Fotos zur Claude Code Schulung und Beratung. Technisch passen danach Lazy Loading Images und React-Entwicklung mit Claude Code.

Beim Testen dieses Ablaufs war die Trennung von Datenvertrag, Komponente, CSS und Review deutlich leichter zu prüfen als ein großer Design-Prompt. Pflichtfelder für width, height und alt haben schwache Daten früh sichtbar gemacht. Die letzte Runde nutzte DevTools Network, Lighthouse und einen mobilen manuellen Check.

#Claude Code #Bildergalerie #React #responsive #performance
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.