Loading States and Skeletons
Loading States and Skeletons
~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
"Loading projects..." as text (chapter 75) WORKS, but feels ABRUPT – skeleton elements (PLACEHOLDERS in the TARGET SHAPE of the content) are the COMMON pattern in MODERN web applications.
A skeleton component
function ProjectListSkeleton() {
return (
<ul>
{Array.from({ length: 5 }).map((_, index) => (
<li key={index} className="skeleton-item">
<div className="skeleton-line" style={{ width: '60%' }} />
</li>
))}
</ul>
);
}
export default ProjectListSkeleton;Array.from({ length: 5 }) generates FIVE placeholder rows, WITHOUT needing real data – key={index} is EXCEPTIONALLY unproblematic HERE, since the list is NEVER re-sorted or EXTENDED while it's VISIBLE.
The CSS animation
.skeleton-line {
height: 1rem;
background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
background-size: 200% 100%;
animation: skeleton-pulse 1.5s ease-in-out infinite;
}
@keyframes skeleton-pulse {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Using it in the project list
function ProjectListPage() {
const { data, isPending, isError, error } = useProjects();
if (isPending) {
return <ProjectListSkeleton />;
}
// ... rest unchanged
}ONE line swap COMPARED to chapter 75 – isPending REMAINS the CONTROLLING condition, ONLY the DISPLAYED element changes, from PLAIN text to a STRUCTURALLY matching preview.
When skeletons pay off
- For load times over ROUGHLY 300-500ms – BELOW that, ANY loading element tends to feel more DISTRACTING than helpful.
- When the TARGET STRUCTURE of the content is KNOWN (list, card, table) – for COMPLETELY unpredictable layouts, a simple spinner remains the more PRAGMATIC choice.
Tipp: TanStack Query's staleTime (chapter 73) ENSURES skeletons appear ONLY on the VERY FIRST load – revisiting the page WITHIN the staleTime shows the cached data IMMEDIATELY, WITHOUT the skeleton flashing again.