Loading States That Feel Fast: Spinners, Skeletons and Honest Progress in React

A glowing violet loading ring with a comet trail floating above a row of frosted glass blocks on a dark background

Waiting is part of the interface

Every product makes people wait. A search query goes to the server, a file uploads, a model thinks before it answers. You can shave milliseconds off each of those, and you should, but some waiting never goes away. What you control is how that time feels.

A good loading state does three jobs. It confirms that the click worked. It tells people roughly what is happening and how long it might take. And it keeps the rest of the page stable so nobody loses their place. Most loading states only manage the first job, and some do not even manage that. This guide covers how to pick the right indicator, when to show it, and the small details that make a wait feel shorter than it is, with examples from today's additions to the Uncode catalog.

Pick the indicator by what you know

The most useful question is not "which spinner looks best" but "what do I know about this wait?" The answer points to a different pattern each time.

  • You know nothing about the duration. Use an indeterminate indicator: a spinner, a pulsing dot, a looping bar. It says "working on it" without making promises.
  • You know the shape of what is coming. Use a skeleton: grey blocks in the layout of the content that will replace them. People start reading the structure before the data arrives.
  • You know how far along you are. Use a determinate progress bar or a percentage. Uploads, exports and multi step jobs almost always have this information, so show it.
  • You already know the result. Skip the loader and update the interface optimistically, then roll back if the server disagrees.

Mixing these up is where most loading states go wrong. A spinner on a ten minute export feels broken after thirty seconds. A skeleton for a single button press is overkill. Match the pattern to the information you actually have.

Spinners: small, quiet and in the right place

A spinner works best close to the thing that triggered it. If someone clicks "Save", the spinner belongs inside that button, replacing the label or sitting next to it, not in a full screen overlay that blocks everything else. Local feedback keeps the rest of the page usable and makes the cause and effect obvious.

Size matters too. Inline spinners should match the text around them, roughly the height of a capital letter. Page level spinners can be larger, but rarely need to be more than 48 pixels. Anything bigger starts to feel like the product is showing off instead of working.

Style is where you can add personality without hurting clarity. The Comet Spinner uses a bright head and a fading tail built entirely from box shadows, so it scales cleanly and inherits its colour from the surrounding text. The Wave Spinner ripples through a grid of dots and comes with several patterns, which makes it easy to give different parts of an app a consistent but distinct feel. For something softer, the Morphing Loader shifts between a rounded square and a circle, and the Weave Spinner works as a more atmospheric centrepiece for splash screens.

Timing: when to show a loader, and when not to

The fastest loading state is the one that never appears. If most requests finish quickly, flashing a spinner for a fraction of a second makes the product feel slower, not faster, because the eye registers a flicker of change that means nothing.

A simple rule works well in practice: wait a short delay before showing any indicator, then once it is visible, keep it on screen for a minimum time so it does not blink. In React that is a small hook with two timers. Start a timer when the request begins; if the request finishes first, never render the loader. If the loader does appear, hold it for a few hundred milliseconds even if the data arrives sooner.

For longer waits, change the message as time passes. "Loading" is fine at first. After several seconds, "Still working, this can take a moment" reassures people that nothing has stalled. After much longer, offer a way out: cancel, retry, or come back later with a notification.

Uncode UI

Build with AI, not against it.

Over 1,400 React and Tailwind components that run live in the browser, with a prompt ready to paste into Claude, Cursor or v0. Plus a community of people building real sites with AI.

Skeletons: promise the layout, not the content

Skeleton screens work because they answer the question people ask while waiting: "what am I about to see?" A list of grey rows tells you a list is coming. A large block and three lines tell you an article card is on its way. The page stops feeling empty.

The key is accuracy. A skeleton that does not match the final layout causes a jump when the real content lands, which is worse than no skeleton at all. Give placeholders the same dimensions, margins and aspect ratios as the content they stand in for. Images are the usual culprit, so always reserve their space with a fixed aspect ratio.

Keep skeleton animation subtle. A slow shimmer or a gentle pulse is enough to signal activity. Fast, high contrast shimmers compete with the content around them and become tiring on pages that load in pieces.

Progress that tells the truth

When you can measure progress, show it, and show it honestly. A bar that races to 90 percent and then sits there for a minute teaches people not to trust your interface. It is better to move slowly and steadily than to fake speed and stall.

For work with distinct stages, name the stages. "Uploading", "Processing", "Almost done" gives people a mental model of what is happening, and a stage that takes longer feels acceptable when they know which one it is. This is especially useful for AI features, where a single request might include retrieval, generation and formatting.

Inputs can carry the feedback too

Not every loading moment needs a separate indicator. Often the control someone is already using is the best place to show that something is happening. A search field can show its state inline. A submit button can turn into a spinner and back.

Inputs can also make the act of submitting feel satisfying, which matters more than it sounds. The Placeholders and Vanish Input rotates example prompts while it waits for input, then dissolves the text into particles on submit, a clear signal that the message has gone. The Gooey Input expands from a compact search button into a full field, so the transition itself explains what just changed. Use effects like these on moments that deserve them, such as a main search or a prompt box, rather than on every form field.

Accessibility and motion

Loading states are easy to overlook for people who cannot see them. A few checks cover most of the ground:

  • Give indicators role="status" and a label such as "Loading" so screen readers announce them without stealing focus.
  • Set aria-busy="true" on the region that is updating, and remove it when the content arrives.
  • Do not move keyboard focus to a spinner. Keep focus where it was, then move it deliberately if the new content needs attention.
  • Respect prefers-reduced-motion. Slow the animation right down or swap it for a static indicator with text. Every loader should still communicate its state with motion turned off.
  • Check contrast. A pale spinner on a dark background can disappear entirely on a dim screen.

Bringing it into your project

Start by listing the waits in your product and writing down what you know about each one: its usual duration, whether you can measure progress, and what the finished content looks like. That list tells you which pattern to use far better than any style guide.

Then pick components that fit. Every loader and input mentioned here is in the Uncode catalog with a live preview and code you can copy. If you build in Claude Code, Cursor or another editor with MCP support, you can also connect the catalog to your editor and ask for a spinner or a skeleton by name, then tune its size, colour and timing in place. The aim is simple: waiting that feels calm, honest and a little shorter than it really is.

Uncode UI

Build with AI, not against it.

Over 1,400 React and Tailwind components that run live in the browser, with a prompt ready to paste into Claude, Cursor or v0. Plus a community of people building real sites with AI.

Keep reading