Use Cases (Mis à jour: 02/06/2026)

Créer une galerie d'images rapide avec Claude Code

Guide pratique: galerie responsive avec Claude Code, React, srcset, lightbox, pièges et vérifications.

Créer une galerie d'images rapide avec Claude Code

Une galerie d’images est une surface produit

Créer une galerie d’images avec Claude Code ne doit pas se limiter à demander une grille esthétique. Une galerie peut vendre un produit, prouver un cas client, documenter un atelier ou aider le lecteur à comparer des captures avant/après. Si le prompt dit seulement “fais une belle galerie masonry”, Claude Code peut produire une démo agréable mais oublier ce qui compte en production: poids des images, texte alt, stabilité du layout, mobile, fermeture au clavier, données CMS cassées et lien vers l’action suivante.

Cette version transforme la tâche en workflow vérifiable. On donne d’abord des contraintes à Claude Code, puis on construit un composant React typé, on ajoute srcset, sizes, chargement différé et lightbox, et on termine par les pièges à contrôler avant publication. Pour compléter, consulte aussi traitement d’image avec Claude Code, optimisation des performances et accessibilité. Les références externes utiles sont la documentation officielle Claude Code, MDN sur les images responsive, MDN sur le Lazy loading et WCAG 2.2.

En pratique, je demande d’abord à Claude Code de figer le contrat de données. Chaque image doit avoir un id, une catégorie, une largeur, une hauteur et un texte alternatif utile. Ensuite seulement, le choix entre img, Next.js Image, Astro ou un CDN devient un détail d’intégration.

Donner des contraintes à Claude Code

Ce prompt sert de base. Il demande du code copiable, des états d’erreur et une revue.

Implémente une galerie d'images en React.
Le but est une UI rapide pour articles, cas clients, captures produit et photos d'atelier.

Contraintes:
- Ne pas casser le routing ni les conventions du design system.
- Définir un type image avec id, src, alt, width, height et category obligatoires.
- Utiliser CSS Grid pour le layout responsive.
- Utiliser srcset, sizes, loading et fetchPriority de manière intentionnelle.
- Ouvrir une lightbox au clic et la fermer avec Escape.
- Gérer tableau vide, image en erreur, alt long et largeur mobile.
- Après l'implémentation, expliquer fichiers modifiés, tests et risques restants.

Retourne du React/TypeScript et du CSS prêts à copier, pas du pseudocode.

Cette formulation force une petite modification vérifiable. width et height réduisent les sauts de layout. alt obligatoire évite les textes vides. La demande de risques pousse Claude Code à revoir son propre patch.

Architecture recommandée

Avant le code, montre le flux. Claude Code sépare mieux les responsabilités quand l’entrée et les vérifications sont visibles.

flowchart LR
  A["Images sources"] --> B["Variantes de tailles"]
  B --> C["Tableau GalleryImage"]
  C --> D["Filtre par catégorie"]
  D --> E["Cartes CSS Grid"]
  E --> F["Lightbox"]
  E --> G["Lighthouse et contrôle manuel"]
DécisionValeur de départÀ revoir si
LayoutCSS GridLes hauteurs varient fortement
Lazy loadingSous le premier écranL’image principale arrive trop tard
VariantesEnviron 480/960/1440pxBeaucoup d’utilisateurs grands écrans
LightboxVersion accessible minimaleLa galerie influence l’achat

Il n’est pas nécessaire d’ajouter une bibliothèque masonry au début. Beaucoup de pages ont surtout besoin de cartes stables, de miniatures rapides et d’un aperçu clair. Moins de dépendances rend aussi la revue du diff plus simple.

Implémentation React copiable

Le composant suivant est volontairement léger. Il fonctionne dans Vite, peut devenir un client component Next.js et peut ensuite utiliser l’abstraction image de ton projet.

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;
}

Dans une application réelle, remplace le img brut par le composant image standard du dépôt si nécessaire. Le contrat alt, width, height et sizes doit rester explicite.

Données et cas d’usage

Garde les données hors du composant. Même avec un CMS, normalise-les avant le rendu.

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",
  },
];

Premier cas: portfolio ou page de cas client. La galerie aide à comparer les résultats, puis mène vers l’article complet ou la consultation.

Deuxième cas: ecommerce ou produit numérique. Captures, exemples d’usage et comparaisons réduisent l’incertitude, mais charger toutes les images haute définition avant le CTA ralentit la page.

Troisième cas: formation, événement ou documentation interne. Tableaux blancs, étapes, avant/après et écrans d’erreur deviennent réutilisables. En interne, vérifie toujours noms de clients, emails et secrets visibles.

Quatrième cas: article technique. Avec plusieurs exemples de code, un diagramme ou une galerie de captures de vérification aide le lecteur à garder le contexte.

Pièges fréquents

Premier piège: mettre en lazy loading l’image immédiatement visible. Elle peut influencer le LCP, donc le premier élément mérite souvent eager et parfois fetchPriority="high". Tout charger en eager est l’erreur inverse.

Deuxième piège: oublier width et height. Les cartes changent de hauteur au chargement et la page semble instable. Demande à Claude Code de chercher les risques de CLS.

Troisième piège: transformer alt en champ SEO. Le texte alternatif doit expliquer l’image quand elle n’est pas visible.

Quatrième piège: une lightbox utilisable seulement à la souris. Il faut un bouton nommé, Escape, un focus visible et un comportement correct sur mobile. Pour un focus trap strict, évalue Radix UI ou React Aria.

Cinquième piège: aucune règle d’exploitation. Une image PNG de 6 Mo envoyée dans le CMS peut ruiner les performances. Ajoute taille maximale, formats, nommage et contrôles dans CLAUDE.md.

Vérification

Avant publication, teste filtre, lightbox, clavier, données vides, images cassées et largeur 375px. Avec Playwright, commence petit:

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();
});

La revue doit être concrète: requêtes initiales, correspondance de srcset et sizes, qualité du alt, rôles des boutons ou liens, overflow mobile, données CMS invalides et fuite éventuelle d’informations privées.

CTA et résultat pratique

Une galerie doit soutenir un objectif. Une image de cas client doit mener au cas complet, une image produit aux détails d’achat, une photo d’atelier à la formation et consultation Claude Code. Pour aller plus loin, lis lazy loading d’images et développement React avec Claude Code.

En testant cette méthode, séparer contrat de données, composant, CSS et revue a rendu le changement plus facile à relire. Les champs width, height et alt obligatoires ont révélé les entrées faibles avant publication. La passe finale a combiné DevTools Network, Lighthouse et une vérification mobile manuelle.

#Claude Code #galerie d'images #React #responsive #performance
Gratuit

PDF gratuit: cheatsheet Claude Code

Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.

Nous protégeons vos données et n'envoyons pas de spam.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.