Part 36: Loading States, Skeleton Screens & Error UI
A blank screen or a plain text "Loading..." message creates a jarring user experience. Modern applications use Skeleton Screens that mirror the final layout shape while content loads.
1. Creating an Animated Skeleton Component
export function Skeleton({ className }) {
return (
<div className={`animate-pulse bg-gray-200 dark:bg-gray-700 rounded ${className}`} />
);
}
export function ArticleCardSkeleton() {
return (
<div className="p-4 border rounded-lg flex gap-4">
{/* Thumbnail skeleton */}
<Skeleton className="w-24 h-24 rounded-md shrink-0" />
<div className="flex-1 space-y-3">
{/* Title skeleton */}
<Skeleton className="h-6 w-3/4" />
{/* Paragraph lines */}
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-1/2" />
</div>
</div>
);
}
2. Implementing Fallback and Retry Flows
export default function ArticleFeed({ data, isLoading, error, onRetry }) {
if (isLoading) {
return (
<div className="space-y-4">
<ArticleCardSkeleton />
<ArticleCardSkeleton />
<ArticleCardSkeleton />
</div>
);
}
if (error) {
return (
<div className="p-6 text-center border border-red-200 bg-red-50 rounded">
<p className="text-red-700 mb-3">Failed to load articles: {error.message}</p>
<button onClick={onRetry} className="btn-primary">
Try Again
</button>
</div>
);
}
return (
<div className="space-y-4">
{data.map(article => (
<div key={article.id} className="p-4 border rounded-lg">
<h3 className="font-bold">{article.title}</h3>
<p>{article.snippet}</p>
</div>
))}
</div>
);
}
Key Takeaways
- Skeleton screens prevent layout shifts (CLS) and improve perceived performance.
- Always provide user-friendly error messages accompanied by actionable retry buttons.
In Part 37, we explore Optimistic UI Updates!

