React Skeleton Loader: echte Formen, kein Layout Shift

Du lädst eine Liste, und eine Sekunde lang bleibt der Bildschirm leer. Also baust du einen React Skeleton Loader ein: graue Balken, die schimmern, bis die Daten da sind. Dann kommen die Daten, und die Seite springt, weil die Balken 16px hoch waren und der Text 20px, und weil aus dem quadratischen Platzhalter ein Foto im Format 16:10 wurde. Dieser Guide zeigt, wann ein Skeleton der richtige Ladezustand ist, wann ein Spinner besser passt, wie du Skeletons wie den echten Inhalt formst und wie du sie ohne Layout Shift austauschst, mit den kostenlosen Komponenten Skeleton, Spinner und Empty State von Wingo UI.
Skeleton vs Spinner: Welchen Ladezustand soll ich nehmen?
Nimm ein Skeleton, wenn du das Layout dessen kennst, was kommt, und einen Spinner, wenn nicht. Die klarste Antwort auf die Frage Skeleton vs Spinner, die wir gefunden haben, gibt Skeleton Screens 101 (opens in a new tab) der Nielsen Norman Group (Juni 2023, zuletzt geprüft im September 2026). Der Artikel beschränkt Skeletons auf das Laden ganzer Seiten; wir nutzen sie auch für seitengroße Bereiche wie einen Feed oder eine Tabelle, weil dieselbe Begründung gilt:
Laut demselben Artikel sollen die grauen Boxen die Struktur der fertigen Seite nachbilden, und Skeletons verkürzen die gefühlte Ladezeit. Gefühlt ist das entscheidende Wort: Die Anfrage dauert genauso lange. Wenn sie vier Sekunden braucht, optimier also auch die Anfrage.
Wie baue ich einen React Skeleton Loader mit Tailwind?
Die minimale Antwort auf die Suche nach „react skeleton loader tailwind“ ist ein einzelnes div mit animate-pulse, einer Tönung, einem Radius und einer festen Größe. Stand Oktober 2026 ist das Skeleton (opens in a new tab) von shadcn/ui genau das: ein div mit animate-pulse rounded-md bg-accent plus deinen Größenklassen. Das funktioniert, aber du musst jeden Block selbst an den echten Inhalt anpassen, die Wartezeit für Screenreader ansagen und den Austausch sauber hinbekommen. Das Skeleton von Wingo UI übernimmt diese Teile:
Der Basisblock hat drei Formen. rect füllt die Zeile und ist 96px hoch, bis du ihm eine Größe gibst, circle ist 40px groß, solange width nichts anderes sagt, und line ist ein 14px hoher Textbalken. width und height nehmen px-Zahlen oder jede CSS-Länge, Tailwind-Klassen funktionieren auch.
animation kann shimmer (Standard), pulse oder none sein. Der Shimmer ist ein Streifen, der sich auf einem Pseudo-Element per transform bewegt statt über eine animierte background-position. So kann der Browser ihn auf dem Compositor laufen lassen, statt jeden Frame neu zu zeichnen. speed skaliert den Zyklus, color und highlight nehmen jede CSS-Farbe. Die Standardfüllung ist die Textfarbe mit 5 % Deckkraft und bleibt damit in Light und Dark Mode sichtbar.
Drück in der Demo unten auf Reload und schau dir die Inseratskarten an: Skeletons und echte Karten haben denselben Rhythmus, deshalb verschiebt der Austausch nichts.
$ npx wingo-ui@latest add skeletonWie forme ich ein Skeleton wie den echten Inhalt?
Auf zwei Wegen: Du umschließt den echten Inhalt, oder du nimmst ein Preset, das ein gängiges Layout nachbildet.
Den echten Inhalt umschließen
Gib das echte Markup als children mit und dazu ein loading-Flag. Während des Ladens rendern die children unsichtbar und inert im Block, sodass der Block exakt ihre Größe annimmt. Ein rect übernimmt auch die Ecken dessen, was er umschließt, etwa eines abgerundeten Fotos oder eines Buttons. Sobald loading false wird, wird der Inhalt genau dort eingeblendet, wo der Block war.
Das README von react-loading-skeleton (opens in a new tab) argumentiert genauso (Stand Oktober 2026): Bau Skeleton-Zustände direkt in deine Komponenten ein, statt separate Skeleton Screens zu pflegen, damit die Styles synchron bleiben. Dort haben die Balken standardmäßig die Höhe der Schriftgröße, und die Grundnutzung importiert eine CSS-Datei. Der Wrapping-Modus hier misst stattdessen das echte Element, sodass auch ein Button oder ein Foto seine echte Box bekommt.
Zum Umschließen brauchst du Inhalt, den man messen kann. Der Modus eignet sich also für Labels, Avatare, Buttons und Text, die du schon hast. Für Elemente, die du noch nicht hast, nimmst du ein Preset.
Ein Preset nehmen
Vier Presets decken die Layouts ab, die am häufigsten geladen werden:
SkeletonCard: ein Medienblock im Format 16:10 (mediaAspectändert ihn), ein Titel und Meta-Zeilen sowie ein Footer mit Preiszeile und runder Aktion.SkeletonList: 56px hohe Zeilen mit 40px-Avatar, Titel, Untertitel und einer Aktion in Button-Größe, mit Haarlinien zwischen den Zeilen.SkeletonTable: ein Header und 48px hohe Zeilen ab 768px, darunter eine Karte pro Zeile, in reinem CSS, sodass das Server-Markup auf dem Smartphone schon stimmt. Das entspricht dem responsiven Tabellen-Pattern, bei dem Zeilen zu Karten werden.SkeletonProfile: ein Cover, ein Avatar, der es überlappt, Name und Meta-Zeilen und eine Reihe Kennzahlen.
Die Zeilenbreiten in den Presets stammen aus festen Listen, nicht aus Math.random(). Zufällige Breiten sehen natürlich aus, aber Server und Client würfeln unterschiedliche Zahlen, und React meldet einen Hydration Mismatch.
Wie vermeide ich Layout Shift, wenn der Inhalt lädt?
Gib Skeleton und Inhalt dieselbe Größe, und lass den reservierten Platz nie zusammenfallen. Googles Guide zur Optimierung von CLS (opens in a new tab) setzt einen guten Cumulative Layout Shift bei 0,1 oder weniger an, empfiehlt, Platz für spät geladene Inhalte mit min-height oder aspect-ratio zu reservieren, und warnt, dass das Entfernen reservierten Platzes die Seite genauso verschiebt wie das Einfügen von Inhalt.
Daraus folgen fünf Regeln:
- Gleiche Zeilenhöhen.
SkeletonCardnutzt eine 20px hohe Titelzeile, 18px hohe Meta-Zeilen und einen 32px hohen Footer, der Balken jeweils mittig in der Zeile. Gib deiner echten Karteleading-5,leading-[18px]undh-8auf denselben Zeilen. - Gleiche Medienbox. Das
aspect-[16/10]des Skeletons muss zumaspect-[16/10]des Bildes passen, nicht zu einer festen Höhe, die nur bei einer Breite stimmt. - Gleiches Grid. Render die Skeletons in denselben Grid-Klassen wie die echten Elemente.
- Ungefähr gleiche Anzahl. Zeig etwa so viele Skeletons wie ein Bildschirm voller echter Elemente. Sechs Platzhalter, aus denen zwei Treffer werden, sind auch ein Shift.
- Behalt die Box, wenn nichts kommt. Wechsle im selben Container zu einem Empty State, statt gar nichts zu rendern.
Hier das ganze Pattern in einer Seite mit dem Next.js App Router. <Suspense> streamt die statische Überschrift sofort und die Inserate, sobald sie bereit sind, so wie es die Next.js-Doku zum Data Fetching (opens in a new tab) beschreibt. Eine Datei loading.tsx neben der Seite macht dasselbe für das ganze Routensegment.
Skeleton und Empty State sind Client Components; eine Server Component kann sie rendern, weil die Props hier reine Daten sind. Die Bildecken sind rounded-[10px], also der Standard radius="md" des Presets.
Wie mache ich Skeleton Loading in React barrierefrei?
Sag die Wartezeit einmal an und halt die grauen Blöcke stumm. Jeder Block ist aria-hidden, und jedes Preset ist eine role="status"-Region mit aria-busy und einem versteckten Label, standardmäßig „Loading“. Setz label auf etwas Konkretes („Rechnungen werden geladen“). Zusätzliche Props landen nach den eigenen Attributen auf dem Root des Presets. Deshalb macht aria-hidden auf den weiteren Karten im Grid oben diese stumm.
Umschlossener Inhalt ist inert, solange er lädt, sodass Tab einen Button überspringt, der noch nicht bereit ist. Unter prefers-reduced-motion wird der Shimmer ausgeblendet und der Pulse stoppt, sodass jeder Block stillsteht. Die NN/g weist darauf hin, dass animierte Skeletons ablenken können, deshalb ist das wichtig. Der Guide zu prefers-reduced-motion zeigt den Hook, der diesen Schalter hydration-sicher hält.
Wann ist ein Spinner die bessere React Ladeanimation?
Wenn es kein Layout gibt, das man vorab zeigen könnte. Ein Spinner ist die richtige React Ladeanimation für ein abgeschicktes Formular, eine Anmeldung, eine Zahlung, Pull to Refresh oder ein einzelnes Panel, dessen Inhalt seine Form ändert. Der Spinner von Wingo UI hat sechs Stile (ring, dots, bars, pulse, orbit und das iOS-ähnliche spokes), die Größen sm, md und lg mit 16, 24 und 40px und ein label, das Screenreader einmal als Statusmeldung vorlesen, ohne andere Ansagen zu unterbrechen.
Seine nützlichste Prop ist delay. Mit delay={300} rendert in den ersten 300 Millisekunden nichts. Eine schnelle Antwort lässt also nie einen Spinner aufblitzen, und bei einer langsamen wird er eingeblendet.
Das min-h-52 reserviert Platz, wie es ein Skeleton tun würde. Für Aktionen lässt du den separaten Spinner weg und nimmst die Prop loading der Button-Komponente. Der Button behält seine Breite, während er einen Spinner zeigt, und loadingText="Speichern" setzt ein Label daneben.
$ npx wingo-ui@latest add spinnerWas ersetzt ein Skeleton, wenn die Anfrage fehlschlägt oder nichts zurückgibt?
Ein Empty State in derselben Box. Der Empty State hat Presets für empty, no-results, error, first-run, no-permission und offline, jedes mit eigenem Icon, eigenem Farbton und eigenem Text. Beim Preset error oder offline ergänzt onRetry einen Button „Try again“, der einen Spinner zeigt, bis das Promise, das du zurückgibst, erledigt ist. Weil onRetry eine Funktion ist, renderst du diesen Fall aus einer Client Component.
Was sind die typischen Fehler beim Skeleton Loading?
Beim Skeleton Loading React-seitig zeigen sich drei Fehler erst, wenn echte Daten und echte Netzwerke ins Spiel kommen.
- Ein Farbton für alles. Ein Skeleton auf einer Karte im selben Grau verschwindet. Die Standardtönung bezieht sich auf die Textfarbe und ist deshalb auf jeder Fläche sichtbar; auf einem Banner in Markenfarbe übergibst du Weiß an
colorundhighlight. - Skeletons für eine Anfrage von 200ms. Wenn die meisten Antworten schnell kommen, blitzt das Skeleton nur auf. Zeig gecachte Daten, oder überbrück die Wartezeit mit einem Spinner mit
delay. - Ein Skeleton, das nie endet. Eine fehlgeschlagene Anfrage muss das Skeleton durch eine Fehlermeldung mit Retry ersetzen, sonst warten die Leute ewig.
Wie füge ich diese Komponenten meinem Projekt hinzu?
Führ npx wingo-ui@latest add skeleton spinner empty-state aus. Die CLI kopiert den Quellcode in dein Projekt, samt der Helfer und Komponenten, die er importiert (der Empty State bringt den Button mit), und installiert die npm-Pakete, die er nutzt (motion, lucide-react und @radix-ui/react-slot). Alle drei sind kostenlos. Wenn jedes Skeleton in deiner App pulsieren statt schimmern soll, stell das einmal in <UIProvider defaults> ein, statt die Prop jedes Mal mitzugeben. Der Basisblock liest den Schlüssel skeleton; SkeletonText und jedes Preset lesen jeweils ihren eigenen (skeleton-text, skeleton-card, skeleton-list, skeleton-table, skeleton-profile), weil sie animation explizit an ihre Blöcke weitergeben. defaults={{ skeleton: { animation: "pulse" }, "skeleton-card": { animation: "pulse" } }} deckt also einfache Blöcke und Karten ab. Mehr Deep Dives zu einzelnen Komponenten findest du in der Kategorie Komponenten-Guides, und der Guide zu React-Formularkomponenten wendet dieselben Regeln für Barrierefreiheit und Mobile auf jedes Formularelement an.
Häufige Fragen
Soll ich ein Skeleton oder einen Spinner verwenden?
Nimm ein Skeleton, wenn du das Layout des ladenden Inhalts kennst, etwa bei einem Feed, einer Liste oder einer ganzen Seite. Nimm einen Spinner für Aktionen wie Speichern oder Anmelden, für ein einzelnes kleines Modul und wenn du nicht vorhersagen kannst, was erscheint. Ab etwa 10 Sekunden zeigst du besser einen Fortschrittsbalken.
Wie baue ich einen Skeleton Loader mit Tailwind CSS?
Die einfachste Version ist ein div mit animate-pulse, einer Tönung wie bg-foreground/5, einem Radius und fester Höhe und Breite. Gib jedem Block die Höhe der Textzeile oder des Bildes, das er ersetzt, sonst springt die Seite, wenn die Daten kommen.
Verursachen Skeleton Screens Layout Shift?
Nur wenn sie eine andere Größe haben als der Inhalt, der sie ersetzt. Übernimm Zeilenhöhen, Abstände, Seitenverhältnisse der Bilder und das Grid, und behalt den reservierten Platz auch dann, wenn die Anfrage nichts zurückgibt.
Sind Skeleton Loader barrierefrei?
Sie können es sein. Versteck die grauen Blöcke vor Screenreadern, lass ein einziges kurzes Label wie „Inserate werden geladen“ ansagen, halt den Fokus aus Inhalten heraus, die noch nicht bereit sind, und stopp den Shimmer, wenn jemand reduzierte Bewegung eingestellt hat.
Ist das Skeleton von Wingo UI kostenlos?
Ja. Skeleton, Spinner und Empty State sind alle kostenlose Komponenten. Du installierst sie mit npx wingo-ui@latest add skeleton spinner empty-state.
- Skeleton
- Ladezustände
- Core Web Vitals
- Barrierefreiheit
- React


