Criar uma galeria de imagens rápida com Claude Code
Implemente uma galeria responsiva com Claude Code, React, srcset, lightbox, armadilhas e validação.
Galeria de imagens também é fluxo de produto
Criar uma galeria de imagens com Claude Code não deve começar por “faça algo bonito”. Uma galeria pode vender um produto, provar um estudo de caso, documentar um workshop ou ajudar o leitor a comparar capturas antes e depois. Se o prompt pede apenas uma galeria masonry elegante, Claude Code pode entregar um demo visualmente bom, mas frágil: imagens pesadas, alt sem significado, layout shift, lightbox sem teclado, dados quebrados vindos do CMS e uma CTA mal posicionada.
Este guia transforma a tarefa em uma implementação revisável. Primeiro damos limites claros para Claude Code, depois construímos um componente React tipado, adicionamos srcset, sizes, lazy loading e lightbox, e por fim revisamos os erros mais comuns. Para complementar, veja processamento de imagens com Claude Code, otimização de performance e acessibilidade. As referências externas são a documentação oficial do Claude Code, MDN sobre imagens responsivas, MDN sobre Lazy loading e WCAG 2.2.
Na prática, eu peço primeiro o contrato de dados. Cada imagem precisa ter id, categoria, largura, altura e texto alternativo útil. Depois disso, trocar img por Next.js Image, Astro ou CDN é uma adaptação, não uma reconstrução.
Prompt com restrições úteis
Use este prompt como base. Ele pede código real, estados de erro e revisão.
Implemente uma galeria de imagens em React.
O objetivo é uma UI rápida para artigos, estudos de caso, screenshots de produto e fotos de workshop.
Condições:
- Não quebrar rotas existentes nem convenções do design system.
- Definir um tipo de imagem com id, src, alt, width, height e category obrigatórios.
- Usar CSS Grid para o layout responsivo.
- Usar srcset, sizes, loading e fetchPriority de forma intencional.
- Abrir lightbox ao clicar e fechar com Escape.
- Tratar array vazio, erro de carregamento, alt longo e largura mobile.
- Depois, explicar arquivos alterados, testes e riscos restantes.
Retorne React/TypeScript e CSS prontos para copiar, não pseudocódigo.
Esse pedido força Claude Code a produzir um patch pequeno e verificável. width e height reduzem layout shift. alt obrigatório evita descrições vazias. A explicação de riscos ajuda a transformar a resposta em uma revisão técnica.
Estrutura recomendada
Antes do código, mostre o fluxo. Isso ajuda Claude Code a separar dados, UI e validação.
flowchart LR
A["Imagens originais"] --> B["Variações por tamanho"]
B --> C["Array GalleryImage"]
C --> D["Filtro por categoria"]
D --> E["Cards CSS Grid"]
E --> F["Lightbox"]
E --> G["Lighthouse e revisão manual"]
| Decisão | Padrão seguro | Reavaliar quando |
|---|---|---|
| Layout | CSS Grid | Alturas forem muito irregulares |
| Lazy loading | Só abaixo da primeira tela | A imagem principal atrasar |
| Variações | Cerca de 480/960/1440px | Telas grandes forem comuns |
| Lightbox | Acessível e simples | A galeria afetar compra ou lead |
Não comece com uma biblioteca masonry sem necessidade. Muitas páginas precisam apenas de cards estáveis, miniaturas rápidas e uma visualização ampliada clara. Menos dependências também tornam o diff mais fácil de revisar.
Implementação React para copiar
O componente abaixo é leve de propósito. Funciona em Vite, pode virar client component no Next.js e pode ser adaptado para o componente de imagem do seu projeto.
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;
}
Em um app real, troque o img pelo componente de imagem padrão se existir. O contrato com alt, width, height e sizes deve continuar explícito.
Modelo de dados e casos de uso
Mantenha os dados fora do componente. Mesmo vindo de CMS, normalize antes de renderizar.
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",
},
];
Primeiro caso: portfólio ou estudo de caso. A galeria deve ajudar a comparar resultados e levar para o artigo completo, contato ou consultoria.
Segundo caso: ecommerce ou produto digital. Screenshots, uso real, comparações e telas pós-compra reduzem dúvidas. Carregar todas as imagens grandes antes da CTA prejudica a conversão.
Terceiro caso: treinamento, eventos e documentação interna. Quadros, passos, antes/depois e erros viram material reutilizável. Em conteúdo interno, revise nomes de clientes, emails e secrets.
Quarto caso: artigos técnicos. Quando há muitos exemplos de código, diagramas e screenshots de validação ajudam o leitor a manter o contexto.
Armadilhas comuns
A primeira armadilha é aplicar lazy loading na imagem visível logo ao abrir a página. Ela pode afetar LCP, então o primeiro item pode precisar de eager e fetchPriority="high". O oposto, carregar tudo de uma vez, também é ruim.
A segunda é omitir width e height. O layout muda a cada imagem carregada e a página parece instável. Peça a Claude Code uma revisão explícita de CLS.
A terceira é tratar alt como campo de palavras-chave. O texto alternativo deve explicar a imagem quando ela não pode ser vista.
A quarta é lightbox só para mouse. Botão de fechar nomeado, Escape, foco visível e comportamento mobile são o mínimo. Para focus trap estrito, avalie Radix UI ou React Aria.
A quinta é não definir operação de imagens. Um PNG de 6 MB no CMS pode arruinar a página. Coloque tamanho máximo, formatos, nomes e revisão em CLAUDE.md.
Verificação antes de publicar
Teste filtros, lightbox, teclado, dados vazios, imagem quebrada e largura de 375px. Com Playwright, comece pequeno:
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();
});
A revisão deve ter critérios fixos: requisições iniciais, coerência de srcset e sizes, qualidade do alt, papéis de botões ou links, overflow mobile, dados ruins do CMS e vazamento de informação privada em screenshots.
CTA e resultado prático
Uma galeria deve apoiar uma rota de negócio. Imagens de caso levam ao caso completo, imagens de produto levam à compra, fotos de workshop levam a treinamento e consultoria Claude Code. Para continuar, veja lazy loading de imagens e desenvolvimento React com Claude Code.
Ao testar esse fluxo, separar contrato de dados, componente, CSS e revisão tornou a mudança mais fácil de auditar. Os campos obrigatórios width, height e alt encontraram entradas fracas antes do deploy. A checagem final combinou DevTools Network, Lighthouse e teste manual em mobile.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.