We use cookies to enhance your experience on the site
CodeWorlds

Fallback UI and Skeleton Loaders

In space, when a station module is initializing, the crew sees progress indicators and holographic outlines of the future interface. In React, we have skeleton loaders and fallback UI - placeholder interface elements that inform the user about ongoing loading.

Why Is Fallback UI Important?

Good loading is not just a spinner. The user should have a sense that something is happening and know what to expect.

Loading Quality Levels (from worst to best)

1// 1. Nothing (worst) - user doesn't know what's happening
2<Suspense fallback={null}>
3
4// 2. Simple text
5<Suspense fallback={<p>Loading...</p>}>
6
7// 3. Spinner/Loader
8<Suspense fallback={<Spinner />}>
9
10// 4. Skeleton loader (best)
11<Suspense fallback={<ContentSkeleton />}>

Skeleton Loaders

A skeleton loader is a placeholder UI that mimics the shape and layout of the target content. The user sees a "skeleton" of the page that gradually fills in with real content.

Basic Skeleton in CSS

1function CardSkeleton() {
2  return (
3    <div className="skeleton-card">
4      <div className="skeleton-image skeleton-pulse" />
5      <div className="skeleton-title skeleton-pulse" />
6      <div className="skeleton-text skeleton-pulse" />
7      <div className="skeleton-text skeleton-pulse short" />
8    </div>
9  );
10}
1.skeleton-pulse {
2  background: linear-gradient(
3    90deg,
4    #e0e0e0 25%,
5    #f0f0f0 50%,
6    #e0e0e0 75%
7  );
8  background-size: 200% 100%;
9  animation: pulse 1.5s infinite;
10  border-radius: 4px;
11}
12
13@keyframes pulse {
14  0% { background-position: 200% 0; }
15  100% { background-position: -200% 0; }
16}
17
18.skeleton-image {
19  width: 100%;
20  height: 200px;
21  margin-bottom: 12px;
22}
23
24.skeleton-title {
25  width: 60%;
26  height: 24px;
27  margin-bottom: 8px;
28}
29
30.skeleton-text {
31  width: 100%;
32  height: 16px;
33  margin-bottom: 6px;
34}
35
36.skeleton-text.short {
37  width: 40%;
38}

Skeletons for Different Content Types

List of Elements

1function ListSkeleton({ count = 3 }) {
2  return (
3    <div className="list-skeleton">
4      {Array.from({ length: count }).map((_, i) => (
5        <div key={i} className="list-item-skeleton">
6          <div className="avatar-skeleton skeleton-pulse" />
7          <div className="info-skeleton">
8            <div className="skeleton-pulse name-skeleton" />
9            <div className="skeleton-pulse detail-skeleton" />
10          </div>
11        </div>
12      ))}
13    </div>
14  );
15}

Dashboard with Multiple Sections

1function DashboardSkeleton() {
2  return (
3    <div className="dashboard-skeleton">
4      <div className="stats-row">
5        <StatCardSkeleton />
6        <StatCardSkeleton />
7        <StatCardSkeleton />
8      </div>
9      <div className="main-content">
10        <ChartSkeleton />
11        <ListSkeleton count={5} />
12      </div>
13    </div>
14  );
15}

Loading Patterns

Progressive Loading

Load important content first, the rest loads in the background:

1function MissionDashboard() {
2  return (
3    <div>
4      {/* Critical content - no lazy loading */}
5      <MissionStatus />
6
7      {/* Less critical - lazy loading with skeleton */}
8      <Suspense fallback={<ChartSkeleton />}>
9        <MissionChart />
10      </Suspense>
11
12      {/* Least important - loads last */}
13      <Suspense fallback={<ListSkeleton count={5} />}>
14        <MissionHistory />
15      </Suspense>
16    </div>
17  );
18}

Inline Loading States

You don't always need Suspense. For asynchronously loaded data, use loading states:

1function CrewMember({ id }) {
2  const [member, setMember] = useState(null);
3  const [loading, setLoading] = useState(true);
4
5  useEffect(() => {
6    fetchCrewMember(id)
7      .then(setMember)
8      .finally(() => setLoading(false));
9  }, [id]);
10
11  if (loading) return <MemberSkeleton />;
12
13  return (
14    <div className="crew-member">
15      <img src={member.avatar} alt={member.name} />
16      <h3>{member.name}</h3>
17      <p>{member.role}</p>
18    </div>
19  );
20}

Best Practices

  1. Skeleton should mirror the content layout - same size and shape
  2. Pulse animation - gives the feeling that something is happening
  3. Avoid flickering - if loading takes <300ms, don't show skeletons
  4. Use different skeletons for different content types
  5. Maintain proportions - the skeleton should take up the same space as the real content
Go to CodeWorlds