Tips & Tricks (Aktualisiert: 2.6.2026)

Skeleton Loading mit Claude Code: React, CLS und Barrierefreiheit

Skeleton Loading mit Claude Code umsetzen: React-Beispiel, CLS, Barrierefreiheit, Fehlerfälle und Checks.

Skeleton Loading mit Claude Code: React, CLS und Barrierefreiheit

Skeleton Loading zeigt die grobe Struktur einer Seite, während Daten noch geladen werden. Einfach gesagt: Bild, Titel, Zusammenfassung und Aktionen bekommen ihren Platz, bevor der echte Inhalt ankommt.

Ein Spinner sagt nur: “Es passiert etwas.” Ein Skeleton sagt zusätzlich: “Hier wird gleich diese Art von Inhalt erscheinen.” Wenn Bild, Werbefläche oder API-Antwort in einen bereits reservierten Bereich geladen werden, springt die Seite weniger. Diese visuelle Stabilität passt direkt zu den Hinweisen von web.dev zu Cumulative Layout Shift und Core Web Vitals.

Dieser Leitfaden zeigt, wie du Claude Code für eine praktische Skeleton-Loading-Implementierung briefst. Enthalten sind kopierbare React- und CSS-Beispiele, Hinweise zu Barrierefreiheit, typische Fehler und leichte Checks. Passend dazu sind Performance-Optimierung, Lazy Loading für Bilder und der Accessibility-Workflow.

Arbeit zuerst sauber aufteilen

Ein Skeleton ist nicht nur ein graues Rechteck. Eine echte Funktion braucht Ladezustand, Erfolg, leeren Zustand und Fehlerzustand. Diese Zustände sollten dieselbe stabile Struktur teilen. Wenn du Claude Code nur “mach ein schönes Skeleton” sagst, entsteht vielleicht ein guter Shimmer, aber Fehlerbehandlung, reduzierte Bewegung oder Screenreader-Hinweise fehlen.

flowchart LR
  P["Claude Code Prompt"] --> S["Skeleton mit ähnlicher Größe"]
  S --> D["geladene Daten"]
  D --> E["leerer Zustand"]
  D --> X["Fehlerzustand"]
  S --> A["aria-busy / status"]
  S --> M["prefers-reduced-motion"]
  S --> C["CLS-Prüfung"]

Beginne mit einem Prompt, der Umfang und Prüfung festlegt:

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 ist eine CSS-Bedingung, die erkennt, ob Nutzer im Betriebssystem oder Browser weniger Bewegung wünschen. Starke Shimmer-Effekte können unangenehm sein. Prüfe deshalb MDNs Referenz zu prefers-reduced-motion und baue eine ruhige Variante ein.

Realistische Einsatzfälle

Skeleton Loading hilft besonders dann, wenn Nutzer ungefähr wissen, was kommt, die Daten aber noch nicht sichtbar sein können.

EinsatzfallWas reserviert wirdWorauf achten
ArtikelkartenThumbnail, zweizeiliger Titel, Zusammenfassung, TagMedienhöhe fixieren, damit Karten nicht springen
DashboardsKPI-Karten, Chart-Rahmen, AktivitätslisteKeine irreführenden Teilzahlen vor dem fertigen Zustand zeigen
E-Commerce-GridsProduktbild, Name, Preis, BewertungKeine veralteten Preise oder Bestände während Refresh vermitteln
Admin-TabellenHeader, Zeilen, AktionsbereichBei stark schwankenden Zeilen auch Pagination prüfen
Beratungs- oder Content-LandingpagesProof-Karten, CTA, FAQSpäte CTAs können den Conversion-Pfad nach unten drücken

In einem ClaudeCodeLab-Beratungsfluss brachte es am meisten, die Höhe der Artikelkarten und des CTA früh zu reservieren. Der Fehler war ein Skeleton, das höher als der fertige Inhalt war. Beim Laden sah alles ruhig aus, danach sprang die Seite nach oben. Ein Skeleton ist keine Dekoration, sondern ein Layout-Vertrag.

Kopierbares React-Beispiel

Füge dieses Beispiel in src/App.tsx eines Vite + React + TypeScript-Projekts ein. Es simuliert Latenz mit setTimeout und lässt dich zwischen Erfolg, leerem Zustand und Fehler wechseln. Genau dieses Maß an Zustandsbeschreibung sollte Claude Code vor Änderungen an einem echten Repository bekommen.

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: "Sichere UI-Diffs mit Claude Code erstellen",
    description: "Erst bestehende Komponenten lesen, dann nur das Ladeerlebnis verbessern.",
    tag: "UX",
  },
  {
    id: 2,
    title: "Bildplatz reservieren, ohne CLS zu erhöhen",
    description: "Medien, Titel und Zusammenfassung vor den echten Daten stabilisieren.",
    tag: "Performance",
  },
  {
    id: 3,
    title: "Barrierefreie Ladezustände",
    description: "aria-busy, Statusmeldungen und reduzierte Bewegung kombinieren.",
    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="Sichtbaren Zustand ändern">
        <button onClick={() => reloadAs("success")}>Erfolg</button>
        <button onClick={() => reloadAs("empty")}>Leer</button>
        <button onClick={() => reloadAs("error")}>Fehler</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" ? "Artikelliste wird geladen" : "Artikelliste geladen"}
        </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">Noch keine passenden Artikel vorhanden.</div>
        )}

        {state === "error" && (
          <div className="state-panel" role="alert">
            Die Artikelliste konnte nicht geladen werden. Bitte später erneut versuchen.
          </div>
        )}
      </section>
    </main>
  );
}

Der wichtigste Accessibility-Punkt ist, das Skeleton selbst nicht übermäßig anzusagen. Ein Screenreader muss nicht hören, dass drei graue Linien vorhanden sind. In diesem Beispiel sind die Skeleton-Kartenaria-hidden, während eine einzigerole="status"-Meldung den Listenstatus beschreibt. MDNs ARIA status role ist dafür die passende Referenz.

CSS für stabile Größe und Bewegung

Speichere dieses CSS alssrc/skeleton-demo.css. Entscheidend ist, dassmin-height, Medienhöhe und Innenabstände zwischen Ladezustand und finalem Zustand ähnlich bleiben. Der Shimmer ist zurückhaltend und stoppt bei reduzierter Bewegung.

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

Dieses CSS priorisiert stabile Flächen statt dramatischen Glanz. Wenn Skeleton-Linien zu zufällig breit sind, fühlt sich der finale Inhalt wie ein anderes Design an. Reserviere lieber einen zweizeiligen Titel, eine zweizeilige Zusammenfassung und einen vorhersehbaren Medienbereich.

Kleiner Playwright-Check

Wenn das Projekt bereits Playwright nutzt, füge einen fokussierten Test hinzu. Er beweist keinen perfekten CLS in der echten Welt, findet aber offensichtliche Rückschritte vor dem Review.

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

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

  await expect(page.getByText("Artikelliste wird geladen")).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: "Fehler" }).click();
  await expect(page.getByRole("alert")).toContainText("konnte nicht geladen werden");
});

Echter CLS hängt auch von Bildern, Werbeplätzen, Fonts, Drittanbieter-Skripten, Netzwerk und Geräten ab. Nutze diesen Test als frühe Warnung und prüfe danach web.dev-Hinweise und Produktionsdaten.

Häufige Fehler

Der erste Fehler ist ein Skeleton, das nicht zur finalen Karte passt. Es sieht beim Laden sauber aus, danach wächst oder schrumpft der echte Inhalt. Reserviere Bildgrößen mitwidth, height oderaspect-ratio und halte auch Platz für Ads und CTAs frei.

Der zweite Fehler ist ein Skeleton für jede schnelle Anfrage. Wenn eine Anfrage meist in 100 ms fertig ist, entsteht nur Flackern. In der Praxis helfen Regeln wie “erst nach 300 ms anzeigen”, “nur beim ersten Laden” oder “alte Daten während Refresh sichtbar lassen”.

Der dritte Fehler ist zu viel Accessibility-Rauschen. Wenn jede Karte ein eigenesrole="status" hat, können Hilfstechnologien dieselbe Meldung wiederholen. Halte die Live-Meldung auf Listenebene und verstecke rein visuelle Formen.

Der vierte Fehler ist ein fehlender Fehlerzustand. Wenn die API scheitert und das Skeleton bleibt, wirkt die Seite weiterhin beschäftigt. Entwerfe Fehler, leeren Zustand und Wiederholen als eigene Zustände.

Der fünfte Fehler ist, Claude Code Produktprioritäten entscheiden zu lassen. Claude Code kann Dateien lesen und Code erzeugen, aber Menschen entscheiden, welcher CTA sichtbar bleiben muss, wie viel Anzeigenfläche reserviert wird und welcher Inhalt zuerst sicher ist.

Review-Prompt für Claude Code

Nach der Implementierung wechselst du in den Review-Modus:

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.

So wird Claude Code vom Implementierer zum Kritiker. Auf Content-Websites beeinflussen Ads, verwandte Beiträge, CTAs und Lazy Images denselben visuellen Fluss. Nutze den CSS-Styling-Guide und die Testing-Strategien, um visuelle Checks, Screenreader-Prüfung und Regressionstests zu trennen.

Verbindung zur Monetarisierung

Skeleton Loading schützt auch Umsatzpfade. Wenn ein Beratungs-CTA, eine Produktkarte, ein Newsletter-Formular oder ein Anzeigenplatz spät erscheint und den Artikel verschiebt, verlieren Leser ihre Position oder klicken unbeabsichtigt. Das schadet Vertrauen und Conversion.

Einzelne Entwickler können mit der kostenlosen Claude Code Cheatsheet ihre Review-Schritte standardisieren. Teams, die Skeletons, Lazy Images, Core Web Vitals und Barrierefreiheit in einen gemeinsamen Engineering-Workflow bringen wollen, können Claude Code Training und Beratung nutzen. Masa kann das bestehende Repository prüfen und daraus konkrete Prompts, Komponenten und Checks ableiten.

Zusammenfassung

Gutes Skeleton Loading ist kein Trick, um Wartezeit zu verstecken. Es reserviert Platz nahe am finalen Bildschirm, reduziert Unsicherheit und vermeidet sichtbare Layout-Bewegung. Gib Claude Code Zielkomponente, Zustände, Maße, Accessibility-Anforderungen und Prüfkommandos gemeinsam.

Beim Test dieses Workflows waren drei Dinge am wirksamsten: Medien- und Titelhöhe zuerst fixieren, Ladezustand auf Listenebene ansagen und Animation bei reduzierter Bewegung stoppen. Wenn nur der Shimmer poliert wird, bleiben leere und fehlerhafte Zustände später liegen und das Review dauert länger. Implementierung und Verifikation gehören zusammen.

#Claude Code #Skeleton Loading #React #UX #Barrierefreiheit
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.