Loading states exist to answer one question for the user: is anything happening? Without an answer, people tap again, reload the page or leave. The right answer depends on how long they wait.
For very short waits, under about a tenth of a second, most people do not notice a delay, so an indicator only adds flicker. A common technique is to wait briefly before showing a spinner, so fast responses never show one at all.
For waits of a second or a few seconds, show feedback where the result will appear. A skeleton screen, a grey outline of the coming layout, works well for lists, cards and profiles because it shows what is coming and reduces layout shift. A spinner works for small areas or when the result has no predictable shape.
For long tasks such as uploads, exports or AI generation, show real progress if you can measure it, explain what is happening and let people cancel or continue with other work. A spinner that turns for a minute with no other information feels like a failure.
The control that started the action needs a state too. A submit button that switches to a loading state, and cannot be pressed twice, prevents duplicate orders and messages. When loading ends, move clearly to success or error rather than silently returning to default.
Animation helps here, but keep it calm and short in a loop, and respect the reduced-motion setting. ExodeUI, which we make, lets you design a loader or a button with loading, success and error states in one file that your code switches between.