πŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
πŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Loading States: Spinners, Skeletons, and Progress Bars

Design effective loading states in React: spinners, skeleton screens, progress bars, and accessible loading announcements.

⚑ Total XP: 0|πŸ’» react XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Loading state fundamentals.

Quick Quiz //

What's the worst possible loading state?


πŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
πŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

A blank screen while data loads reads as broken, not in-progress. This lesson covers the real toolkit for loading UI: when a simple spinner is enough, when skeleton screens reduce perceived wait time, honest progress bars, and making loading states accessible to screen readers.

1A Blank Screen Is Never the Right Answer

Showing nothing at all while data loads β€” a blank area with no indicator β€” reads to users as broken rather than in-progress. Every loading state is a genuine design decision about what a user should see, and for how long, before real content is ready.

2Spinners: Simple, but Communicate Little

A spinner is the simplest loading indicator, communicating that something is happening but nothing about how much progress has been made or how long it will take. It's appropriate for quick operations, but starts feeling like uncertainty rather than progress for longer waits.

3Skeleton Screens: Showing the Shape of What's Coming

A skeleton screen renders placeholder blocks matching the layout of the actual content β€” a shape where an avatar will be, lines where text will appear. This reduces perceived wait time by letting users understand the page's structure before real data arrives.

4Progress Indicators for Long, Measurable Operations

When an operation's completion percentage is genuinely known β€” like bytes transferred during a file upload β€” a real progress bar is more honest and reassuring than a generic spinner. A fabricated or inaccurate progress percentage erodes user trust faster than an honest spinner would.

5Accessible Loading Announcements

A visual spinner alone provides no information to screen reader users unless the loading region uses aria-live='polite' (or role='status') and aria-busy='true' while content is being replaced, ensuring the loading state is communicated beyond just visual cues.

6Step-by-Step Breakdown

A Blank Screen Is Never the Right Answer. While data is loading, showing nothing at all β€” a blank white area β€” reads as broken, not 'in progress.' Every loading state is a design decision: what should the user see, and for how long, before the real content is ready?

Spinners: Simple, but Communicate Little. A spinner is the easiest loading indicator to build and communicates 'something is happening,' but nothing about HOW MUCH is happening or how long it'll take. For quick loads (under ~1 second), that's fine. For anything longer, a spinner alone starts to feel like uncertainty rather than progress.

Why does a spinner alone start to feel inadequate for a loading operation taking several seconds?

  • β†’It communicates nothing about how much progress has been made or is left
  • β†’Spinners always animate too quickly for slow connections

Skeleton Screens: Showing the Shape of What's Coming. A skeleton screen renders gray placeholder blocks matching the LAYOUT of the real content β€” a rectangle where the avatar will be, lines where text will appear. This reduces perceived wait time because the user already understands the page's structure before any real data has arrived.

Progress Indicators for Long, Measurable Operations. When you actually know how much of an operation has completed β€” a file upload reporting bytes transferred β€” a progress bar with a real percentage is far more honest and reassuring than a generic spinner. Never fake a progress percentage; an inaccurate progress bar erodes trust faster than an honest spinner.

When is a real, numeric progress bar the appropriate loading indicator instead of a generic spinner?

  • β†’When you actually know a real, measurable percentage of completion
  • β†’It's always strictly better than a spinner, in every situation

Accessible Loading Announcements. A visual spinner alone tells sighted users something is loading, but a screen reader user gets no information at all unless the loading region uses aria-live="polite" (or role="status"), and aria-busy="true" while content is being replaced.

Mastery Achieved. You now have a real loading-state toolkit: spinners for quick operations, skeleton screens for reducing perceived wait time, honest progress bars for measurable operations, and accessible announcements via aria-live and role="status". Next, you'll learn Empty States β€” what to show when loading finishes but there's genuinely nothing to display.

Level Up πŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Use aria-live='polite', Not 'assertive', for Routine Loading

Polite live regions wait for a natural pause before announcing, avoiding interrupting a screen reader user mid-task β€” reserve assertive announcements for genuinely urgent updates, not routine loading states.

2Skeleton Screens Should Be Hidden from Screen Readers, Not Read Aloud Element by Element

Give a skeleton container aria-hidden='true' (paired with a text-based loading announcement elsewhere) so screen readers don't tediously narrate a series of meaningless placeholder shapes.

SEO Implications

  • 1

    Loading States Are Irrelevant to Crawlers If Content Is Server-Rendered

    For SEO-critical content, prefer server-rendering the actual content over relying on a client-side loading state pattern, since crawlers generally don't wait for or interpret client-driven loading indicators the way a real user would.

Best Practices

Match the Loading Indicator to the Operation's Duration and Predictability

Use a spinner for quick, unpredictable-duration loads; a skeleton for layout-heavy content loads; and a real progress bar only when an accurate percentage is genuinely available.

Never Show a Loading Indicator for an Instant, Sub-100ms Operation

A flash of a spinner that immediately disappears feels more jarring than useful β€” consider a brief delay before showing a loading indicator for very fast operations to avoid this flicker.

Frequent Bugs

THE BUG

A loading spinner flickers on screen for a fraction of a second even for fast responses, creating a jarring flash.

THE FIX

Add a short delay (e.g. 200ms) before showing the loading indicator, only displaying it if the operation is still pending after that delay β€” this avoids the flash for genuinely fast responses while still showing a spinner for slower ones.

THE BUG

Screen reader users report no indication that a page section is loading.

THE FIX

The loading UI lacks proper ARIA β€” add role='status' and aria-live='polite' to the loading container, and aria-busy='true' while content is being replaced.

Real-World Examples

A Skeleton Screen for a Social Feed

A social media feed's posts take a moment to load on a slow connection. Rendering a PostSkeleton component β€” gray rounded rectangles matching the shape of an avatar, name, and post text β€” for each expected post gives users an immediate sense of the page's structure, reducing perceived wait time compared to a single centered spinner.

function FeedSkeleton() {
  return (
    <div role="status" aria-live="polite" aria-label="Loading posts">
      {[1, 2, 3].map(i => <PostSkeleton key={i} />)}
    </div>
  );
}

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Rendering null while data is loading, leaving a blank area with no indicator

// Wrong {isLoading ? null : <Content />} // Correct {isLoading ? <Spinner /> : <Content />}

The Solution //

Always render some loading indicator β€” even a simple spinner β€” so users understand content is on its way, rather than assuming the page is broken.

The Error //

A fabricated or inaccurate progress percentage shown to make an indeterminate wait feel more informative

// Wrong: fake progress with no real basis <progress value={Math.random() * 100} max={100} /> // Correct: only for genuinely measurable operations <progress value={uploadedBytes} max={totalBytes} />

The Solution //

Only show a numeric progress bar when a real, measurable percentage is available. For indeterminate waits, use a spinner or an animated skeleton instead of a fake percentage.

Lesson Glossary

[01]Skeleton Screen

A placeholder UI matching the layout shape of incoming content, reducing perceived wait time.

Code Preview
<div className="skeleton-avatar" />

[02]Progress Indicator

A UI element showing a real, measurable percentage of an operation's completion.

Code Preview
<progress value={uploaded} max={total} />

[03]role="status"

An ARIA role marking a region as a live status message, announced by screen readers.

Code Preview
<div role="status">

[04]aria-live="polite"

An ARIA attribute causing screen readers to announce content changes without interrupting the user.

Code Preview
aria-live="polite"

[05]aria-busy

An ARIA attribute signaling that a region's content is currently being updated or loaded.

Code Preview
aria-busy={isLoading}

Continue Learning