Use Cases (Diperbarui: 2/6/2026)

Membangun Galeri Gambar Cepat dengan Claude Code

Bangun galeri gambar responsif dengan Claude Code, React, srcset, lightbox, jebakan, dan pemeriksaan.

Membangun Galeri Gambar Cepat dengan Claude Code

Galeri gambar adalah bagian dari alur produk

Membangun galeri gambar dengan Claude Code bukan sekadar meminta layout yang indah. Galeri bisa menjual produk, membuktikan studi kasus, mendokumentasikan workshop, atau membantu pembaca membandingkan screenshot sebelum dan sesudah. Jika prompt hanya berbunyi “buat galeri masonry yang bagus”, Claude Code mungkin memberi demo menarik tetapi melewatkan detail produksi: ukuran gambar, teks alt, layout shift, lebar mobile, lightbox yang bisa ditutup dengan keyboard, data CMS yang rusak, dan CTA setelah gambar dilihat.

Panduan ini memperlakukan galeri sebagai fitur yang siap dipublikasikan. Kita akan memberi batasan ke Claude Code, membuat komponen React bertipe, memakai srcset, sizes, lazy loading, lightbox, lalu memeriksa jebakan umum sebelum rilis. Untuk fondasi terkait, baca juga pemrosesan gambar Claude Code, optimasi performa, dan aksesibilitas. Referensi eksternal yang dipakai: dokumentasi resmi Claude Code, MDN tentang responsive images, MDN tentang Lazy loading, dan WCAG 2.2.

Aturan praktis saya: tetapkan kontrak data sebelum mempercantik tampilan. Setiap gambar harus punya id, kategori, lebar, tinggi, dan alt yang bermakna. Setelah itu, mengganti img mentah dengan Next.js Image, Astro asset, atau CDN image component menjadi keputusan integrasi, bukan perubahan besar.

Prompt untuk Claude Code

Prompt berikut bisa langsung dipakai. Fokusnya adalah kode nyata, keadaan gagal, dan review.

Implementasikan galeri gambar dengan React.
Tujuannya adalah UI cepat untuk artikel, studi kasus, screenshot produk, dan foto workshop.

Syarat:
- Jangan merusak routing atau konvensi design system yang ada.
- Definisikan tipe gambar dengan id, src, alt, width, height, dan category wajib.
- Gunakan CSS Grid untuk layout responsif.
- Gunakan srcset, sizes, loading, dan fetchPriority secara sengaja.
- Klik membuka lightbox dan Escape menutupnya.
- Tangani array kosong, gambar gagal dimuat, alt panjang, dan lebar mobile.
- Setelah implementasi, jelaskan file berubah, test, dan risiko tersisa.

Kembalikan React/TypeScript dan CSS siap salin, bukan pseudocode.

Prompt ini membuat Claude Code menghasilkan patch kecil yang bisa direview. width dan height mengurangi layout shift. alt wajib mencegah teks kosong seperti “gambar 1”. Permintaan risiko membuat Claude Code melakukan pemeriksaan kedua, bukan hanya menulis kode.

Bentuk arsitektur

Sebelum kode, tunjukkan alurnya. Claude Code biasanya lebih rapi saat melihat hubungan antara sumber gambar, data, UI, dan validasi.

flowchart LR
  A["Gambar asli"] --> B["Varian ukuran"]
  B --> C["Array GalleryImage"]
  C --> D["Filter kategori"]
  D --> E["Kartu CSS Grid"]
  E --> F["Lightbox"]
  E --> G["Lighthouse dan cek manual"]
KeputusanDefault amanEvaluasi lagi saat
LayoutCSS GridTinggi gambar sangat bervariasi
Lazy loadingHanya di bawah layar pertamaGambar utama terlambat muncul
Varian gambarSekitar 480/960/1440pxBanyak pengguna layar besar
LightboxPerilaku aksesibel minimalGaleri memengaruhi pembelian

Jangan langsung menambahkan library masonry. Banyak halaman hanya butuh kartu stabil, thumbnail cepat, dan preview yang jelas. Semakin sedikit dependensi, semakin mudah diff dari Claude Code diperiksa.

Implementasi React siap salin

Komponen ini sengaja ringan. Ia bisa dipakai di Vite, dipindah ke client component Next.js, atau diadaptasi ke komponen gambar internal.

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

Di aplikasi nyata, img mentah bisa diganti dengan komponen gambar standar. Kontrak alt, width, height, dan sizes tetap harus jelas.

Model data dan use case

Letakkan data di luar komponen. Walaupun berasal dari CMS, normalisasi dulu sebelum render.

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 pertama adalah portofolio atau studi kasus. Gambar membantu pembaca membandingkan hasil, lalu menuju artikel lengkap, halaman kontak, atau konsultasi.

Use case kedua adalah ecommerce atau produk digital. Screenshot, contoh pemakaian, perbandingan, dan preview setelah pembelian mengurangi keraguan. Namun memuat semua gambar besar sebelum CTA akan memperlambat halaman.

Use case ketiga adalah training, event, dan knowledge base internal. Foto papan, langkah kerja, before/after, dan error screen menjadi materi yang bisa dipakai ulang. Untuk internal, periksa nama pelanggan, email, token, dan data pribadi.

Use case keempat adalah artikel teknis. Jika artikel berisi banyak contoh kode, diagram konsep dan screenshot verifikasi membantu pembaca menjaga konteks.

Jebakan umum

Jebakan pertama adalah memberi lazy loading pada gambar yang langsung terlihat. Gambar itu bisa memengaruhi LCP, jadi item pertama sering perlu eager dan fetchPriority="high". Sebaliknya, membuat semua gambar eager juga memperlambat awal halaman.

Jebakan kedua adalah menghilangkan width dan height. Tinggi kartu berubah saat gambar dimuat dan halaman terasa tidak stabil. Minta Claude Code memeriksa risiko CLS secara eksplisit.

Jebakan ketiga adalah menjadikan alt sebagai daftar kata kunci SEO. Alt harus menjelaskan makna gambar ketika gambar tidak terlihat.

Jebakan keempat adalah lightbox yang hanya bisa dipakai mouse. Tombol close bernama, Escape, fokus terlihat, dan perilaku mobile adalah minimum. Untuk focus trap ketat, evaluasi Radix UI atau React Aria.

Jebakan kelima adalah tidak punya aturan operasi gambar. Satu PNG 6 MB dari CMS bisa merusak performa. Masukkan batas ukuran, format, aturan nama file, dan checklist review ke CLAUDE.md.

Verifikasi sebelum publikasi

Uji filter, lightbox, keyboard, data kosong, gambar rusak, dan lebar 375px. Dengan Playwright, mulai dari test kecil:

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

Review harus punya kriteria tetap: jumlah request awal, kecocokan srcset dan sizes, kualitas alt, role tombol atau link, overflow mobile, data CMS rusak, dan kebocoran informasi pribadi di screenshot.

CTA dan hasil praktik

Galeri harus mendukung jalur bisnis. Gambar studi kasus mengarah ke studi lengkap, gambar produk ke detail pembelian, foto workshop ke training dan konsultasi Claude Code. Untuk lanjutan teknis, baca lazy loading gambar dan pengembangan React dengan Claude Code.

Saat mencoba alur ini, memisahkan kontrak data, komponen, CSS, dan review membuat perubahan jauh lebih mudah diaudit. Field wajib width, height, dan alt menangkap data lemah sebelum deploy. Pemeriksaan akhir memakai DevTools Network, Lighthouse, dan pengujian manual di mobile.

#Claude Code #galeri gambar #React #responsif #performance
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.