Tips & Tricks (Diperbarui: 2/6/2026)

Skeleton Loading dengan Claude Code: React, CLS, dan Aksesibilitas

Buat skeleton loading dengan Claude Code: contoh React, kontrol CLS, aksesibilitas, kegagalan umum, dan checks.

Skeleton Loading dengan Claude Code: React, CLS, dan Aksesibilitas

Skeleton loading adalah pola UI yang menampilkan kerangka halaman saat data masih dimuat. Dalam bahasa sederhana, kita memesan tempat untuk gambar, judul, ringkasan, dan tombol sebelum konten asli datang.

Spinner hanya memberi tahu bahwa “sesuatu sedang berjalan”. Skeleton juga memberi tahu “jenis konten apa yang akan muncul di sini”. Jika gambar, slot iklan, atau respons API masuk ke ruang yang sudah disiapkan, halaman lebih kecil kemungkinan meloncat. Stabilitas visual ini sejalan dengan penjelasan web.dev tentang Cumulative Layout Shift dan Core Web Vitals.

Panduan ini menjelaskan cara meminta Claude Code membuat skeleton loading yang praktis, lalu membahas contoh React dan CSS yang bisa disalin, catatan aksesibilitas, kasus gagal, dan pemeriksaan ringan. Untuk konteks terkait, baca juga optimasi performa, lazy loading gambar, dan workflow aksesibilitas.

Pecah pekerjaan sebelum implementasi

Skeleton bukan sekadar kotak abu-abu. Fitur nyata membutuhkan state loading, success, empty, dan error, dan semua state itu harus berbagi layout yang stabil. Jika Anda hanya meminta Claude Code “buat skeleton yang bagus”, hasilnya mungkin punya shimmer menarik, tetapi melewatkan state error, reduced motion, atau pesan untuk screen reader.

flowchart LR
  P["Prompt Claude Code"] --> S["skeleton dengan ukuran serupa"]
  S --> D["data selesai dimuat"]
  D --> E["empty state"]
  D --> X["error state"]
  S --> A["aria-busy / status"]
  S --> M["prefers-reduced-motion"]
  S --> C["cek CLS"]

Mulai dengan prompt yang menjelaskan scope dan verifikasi:

Read the existing card/list components before editing.
Implement skeleton loading only for the article cards list.
Keep the skeleton dimensions close to the loaded content.
Handle loading, empty, error, and success states.
Respect prefers-reduced-motion and avoid layout shift.
Add a small Playwright check if the project already uses Playwright.
Do not change unrelated styles, routing, or data fetching.

prefers-reduced-motion adalah kondisi CSS untuk mengetahui apakah pengguna meminta gerakan yang lebih sedikit di sistem atau browser. Efek shimmer yang kuat bisa mengganggu sebagian orang, jadi rujuk MDN tentang prefers-reduced-motion dan siapkan versi tanpa animasi.

Use case yang realistis

Skeleton loading paling berguna ketika pengguna bisa menebak konten yang akan muncul, tetapi data belum siap ditampilkan.

Use caseRuang yang disiapkanHal yang perlu dijaga
Daftar kartu artikelThumbnail, judul dua baris, ringkasan, tagTinggi media harus stabil agar kartu tidak meloncat
DashboardKartu KPI, bingkai grafik, aktivitas terbaruJangan tampilkan angka parsial yang menyesatkan
Grid ecommerceGambar produk, nama, harga, ratingJangan buat harga atau stok lama terlihat valid
Tabel adminHeader, baris, area tombol aksiJika jumlah baris sering berubah, cek pagination juga
Landing page konsultasiProof card, CTA, FAQCTA yang muncul terlambat bisa mendorong jalur konversi

Saat Masa mencoba ini di alur konsultasi ClaudeCodeLab, perubahan paling terasa adalah memesan tinggi kartu artikel dan CTA sebelum data datang. Kesalahan yang muncul adalah skeleton dibuat lebih tinggi dari konten final. Saat loading terlihat rapi, tetapi setelah selesai halaman bergerak ke atas. Skeleton bukan dekorasi; ia adalah kontrak layout.

Contoh React yang bisa disalin

Tempel contoh ini ke src/App.tsx pada proyek Vite + React + TypeScript. Kode ini mensimulasikan latensi dengan setTimeout dan memungkinkan Anda berpindah antara success, empty, dan error. Ini juga level detail state yang sebaiknya diberikan ke Claude Code sebelum ia mengedit repository nyata.

import { useEffect, useState } from "react";
import "./skeleton-demo.css";

type Article = {
  id: number;
  title: string;
  description: string;
  tag: string;
};

type LoadState = "loading" | "success" | "empty" | "error";

const demoArticles: Article[] = [
  {
    id: 1,
    title: "Membuat diff UI lebih aman dengan Claude Code",
    description: "Baca komponen yang ada, lalu perbaiki hanya pengalaman loading.",
    tag: "UX",
  },
  {
    id: 2,
    title: "Memesan ruang gambar tanpa menambah CLS",
    description: "Tetapkan tinggi media, judul, dan ringkasan sebelum data asli datang.",
    tag: "Performance",
  },
  {
    id: 3,
    title: "State loading yang aksesibel",
    description: "Gabungkan aria-busy, pesan status, dan perilaku reduced motion.",
    tag: "A11y",
  },
];

function SkeletonLine({ width = "100%" }: { width?: string }) {
  return <span className="sk-line" style={{ width }} aria-hidden="true" />;
}

function ArticleCardSkeleton() {
  return (
    <article className="article-card is-skeleton" aria-hidden="true">
      <div className="sk-media" />
      <div className="article-card__body">
        <SkeletonLine width="46%" />
        <SkeletonLine />
        <SkeletonLine width="86%" />
        <SkeletonLine width="32%" />
      </div>
    </article>
  );
}

function ArticleCard({ article }: { article: Article }) {
  return (
    <article className="article-card">
      <div className="article-card__media">{article.tag}</div>
      <div className="article-card__body">
        <p className="article-card__tag">{article.tag}</p>
        <h2>{article.title}</h2>
        <p>{article.description}</p>
      </div>
    </article>
  );
}

export default function App() {
  const [state, setState] = useState<LoadState>("loading");
  const [articles, setArticles] = useState<Article[]>([]);

  useEffect(() => {
    const timer = window.setTimeout(() => {
      setArticles(demoArticles);
      setState("success");
    }, 1200);

    return () => window.clearTimeout(timer);
  }, []);

  const reloadAs = (nextState: LoadState) => {
    setState("loading");
    setArticles([]);

    window.setTimeout(() => {
      setArticles(nextState === "success" ? demoArticles : []);
      setState(nextState);
    }, 700);
  };

  return (
    <main className="demo-shell">
      <div className="demo-toolbar" aria-label="Ubah state yang terlihat">
        <button onClick={() => reloadAs("success")}>Sukses</button>
        <button onClick={() => reloadAs("empty")}>Kosong</button>
        <button onClick={() => reloadAs("error")}>Error</button>
      </div>

      <section
        aria-busy={state === "loading"}
        aria-describedby="article-list-status"
        className="article-grid"
      >
        <p id="article-list-status" className="sr-only" role="status">
          {state === "loading" ? "Daftar artikel sedang dimuat" : "Daftar artikel selesai dimuat"}
        </p>

        {state === "loading" &&
          Array.from({ length: 3 }).map((_, index) => (
            <ArticleCardSkeleton key={index} />
          ))}

        {state === "success" &&
          articles.map((article) => (
            <ArticleCard key={article.id} article={article} />
          ))}

        {state === "empty" && (
          <div className="state-panel">Belum ada artikel yang bisa ditampilkan.</div>
        )}

        {state === "error" && (
          <div className="state-panel" role="alert">
            Daftar artikel tidak dapat dimuat. Coba lagi nanti.
          </div>
        )}
      </section>
    </main>
  );
}

Detail aksesibilitas terpenting adalah tidak mengumumkan skeleton secara berlebihan. Screen reader tidak perlu mendengar bahwa ada tiga garis abu-abu. Pada contoh ini kartu skeleton memakaiaria-hidden, sementara saturole="status" menjelaskan status daftar. Referensi yang relevan adalah ARIA status role di MDN.

CSS untuk ukuran dan gerakan yang stabil

Simpan CSS berikut sebagaisrc/skeleton-demo.css. Kuncinya adalah menjagamin-height, tinggi media, dan spacing body tetap mirip antara state loading dan loaded. Shimmer dibuat tenang dan berhenti saat pengguna memilih reduced motion.

:root {
  color: #18212f;
  background: #f6f7f9;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

button {
  min-height: 40px;
  border: 1px solid #b8c2d6;
  border-radius: 8px;
  background: #ffffff;
  color: #18212f;
  padding: 0 14px;
  font-weight: 700;
}

.demo-shell {
  width: min(1040px, calc(100% - 32px));
  margin: 40px auto;
}

.demo-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

.article-card {
  min-height: 316px;
  overflow: hidden;
  border: 1px solid #d7deea;
  border-radius: 8px;
  background: #ffffff;
}

.article-card__media,
.sk-media {
  display: grid;
  min-height: 148px;
  place-items: center;
  background: #dfe7f3;
  color: #39506f;
  font-weight: 800;
}

.article-card__body {
  display: grid;
  gap: 10px;
  padding: 18px;
}

.article-card__tag {
  color: #3b6b4f;
  font-size: 0.875rem;
  font-weight: 800;
}

.article-card h2 {
  min-height: 56px;
  margin: 0;
  font-size: 1.16rem;
  line-height: 1.45;
}

.article-card p {
  margin: 0;
  line-height: 1.7;
}

.sk-line,
.sk-media {
  border-radius: 8px;
  background: linear-gradient(90deg, #d9e0ea 25%, #edf1f7 37%, #d9e0ea 63%);
  background-size: 240% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

.sk-line {
  display: block;
  height: 16px;
}

.state-panel {
  min-height: 180px;
  display: grid;
  place-items: center;
  border: 1px solid #d7deea;
  border-radius: 8px;
  background: #ffffff;
  padding: 24px;
  text-align: center;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

@keyframes skeleton-shimmer {
  from {
    background-position: 120% 0;
  }

  to {
    background-position: -120% 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sk-line,
  .sk-media {
    animation: none;
    background: #d9e0ea;
  }
}

CSS ini mengutamakan layout stabil daripada kilau dramatis. Jika lebar garis skeleton terlalu acak, konten final terasa seperti desain lain. Lebih aman memesan judul dua baris, ringkasan dua baris, dan area media yang konsisten.

Pemeriksaan kecil dengan Playwright

Jika proyek sudah memakai Playwright, tambahkan satu test yang fokus. Ini tidak membuktikan CLS dunia nyata sempurna, tetapi menangkap regresi jelas sebelum review.

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

test("article skeleton keeps a stable card area", async ({ page }) => {
  await page.goto("/");

  await expect(page.getByText("Daftar artikel sedang dimuat")).toBeAttached();
  await expect(page.locator(".is-skeleton")).toHaveCount(3);

  const firstBox = await page.locator(".article-card").first().boundingBox();
  expect(firstBox?.height).toBeGreaterThan(280);

  await page.getByRole("button", { name: "Error" }).click();
  await expect(page.getByRole("alert")).toContainText("tidak dapat dimuat");
});

CLS nyata tetap dipengaruhi gambar, iklan, font, script pihak ketiga, jaringan, dan perangkat. Jadikan test ini alarm awal, lalu gunakan panduan web.dev dan data produksi untuk keputusan yang lebih kuat.

Kesalahan umum

Kesalahan pertama adalah skeleton yang tidak cocok dengan konten final. Saat loading terlihat rapi, tetapi kartu asli melebar atau menyusut. Tetapkan dimensi gambar denganwidth, height, atauaspect-ratio, dan siapkan ruang untuk iklan serta CTA sebelum render.

Kesalahan kedua adalah menampilkan skeleton untuk semua request cepat. Jika request biasanya selesai dalam 100 ms, skeleton justru membuat flicker. Di produksi, gunakan aturan seperti “tampilkan setelah 300 ms”, “hanya pada load pertama”, atau “pertahankan data lama saat refresh”.

Kesalahan ketiga adalah terlalu banyak suara aksesibilitas. Jika setiap kartu punyarole="status", assistive technology bisa mengulang pesan. Simpan live message di level daftar dan sembunyikan bentuk visual skeleton.

Kesalahan keempat adalah lupa state gagal. Jika API gagal dan skeleton tetap ada, pengguna mengira halaman masih berjalan. Desain error, empty, dan retry sebagai state terpisah.

Kesalahan kelima adalah meminta Claude Code menentukan prioritas produk. Claude Code bisa membaca file dan membuat diff, tetapi manusia tetap menentukan CTA mana yang harus terlihat, berapa ruang iklan yang disiapkan, dan konten apa yang aman tampil dulu.

Prompt review untuk Claude Code

Setelah implementasi, pindahkan Claude Code ke mode review:

Review only the skeleton loading changes.
Check whether loaded content and skeleton content reserve similar space.
Check loading, success, empty, and error states.
Check reduced-motion behavior and ARIA announcements.
Point out any code that may increase CLS or create repeated screen reader messages.
Return findings with file names and exact lines.

Ini mengubah Claude Code dari implementer menjadi reviewer kritis. Pada situs konten, iklan, artikel terkait, CTA, dan lazy images memengaruhi alur visual yang sama. Gunakan panduan CSS styling dan strategi testing untuk memisahkan cek visual, screen reader, dan regresi.

Hubungan dengan monetisasi

Skeleton loading juga melindungi jalur pendapatan. Jika CTA konsultasi, kartu produk, form newsletter, atau iklan muncul terlambat dan mendorong artikel, pembaca kehilangan posisi atau mengklik area yang salah. Itu merusak trust dan conversion.

Developer individu bisa mulai dari cheatsheet Claude Code gratis untuk menstandarkan langkah review. Tim yang ingin menjadikan skeleton loading, lazy images, Core Web Vitals, dan aksesibilitas sebagai workflow bersama bisa memakai training dan konsultasi Claude Code. Masa dapat meninjau repository yang ada dan mengubah perbaikan menjadi prompt, komponen, dan checks yang praktis.

Ringkasan

Skeleton loading yang baik bukan trik untuk menyembunyikan waktu tunggu. Ia memesan ruang yang dekat dengan layar final, mengurangi ketidakpastian, dan menekan gerakan layout yang terlihat. Saat memakai Claude Code, berikan komponen target, state, dimensi, kebutuhan aksesibilitas, dan command verifikasi sekaligus.

Setelah mencoba workflow ini, perubahan paling berguna adalah menetapkan tinggi media dan judul lebih dulu, mengumumkan loading di level daftar, dan menghentikan animasi untuk pengguna reduced motion. Saat hanya fokus pada shimmer, empty dan error state tertunda dan review menjadi lebih lama. Minta implementasi dan verifikasi dalam satu paket.

#Claude Code #skeleton loading #React #UX #aksesibilitas
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.