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.
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écision | Valeur de départ | À revoir si |
|---|---|---|
| Layout | CSS Grid | Les hauteurs varient fortement |
| Lazy loading | Sous le premier écran | L’image principale arrive trop tard |
| Variantes | Environ 480/960/1440px | Beaucoup d’utilisateurs grands écrans |
| Lightbox | Version accessible minimale | La 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.
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.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.