← Blog
25 Sept 2026spinner componentVueloading statesaccessibilityprogress indicator

Spinner Component Guide: Choose the Right Loading State for Vue Apps

Learn when to use a spinner component, progress bar, skeleton, or status message, then implement accessible Vue loading states without blocking useful interaction.

Spinner Component Guide: Choose the Right Loading State for Vue Apps

A spinner component works best when work has started, the wait is short or unknown, and users need confirmation that the interface is responding. It should not be the default answer to every asynchronous task.

For dashboards, forms, and application layouts, we recommend a local loading state first. Put the signal in the button, panel, row, or region that is waiting, while leaving unrelated work available. Choose a progress indicator when completion can be measured, a skeleton when the final layout is known, and a text status when work continues in the background.

This guide shows how to make that decision, build a dependable Vue request state, and verify that the experience remains accessible.

Table of contents

Before you start: define the async boundary

Before adding an indicator, define three things:

  • Trigger: What action or lifecycle event starts the request?
  • Scope: Is one button, field, dashboard panel, or route waiting?
  • Completion contract: What counts as success, failure, timeout, cancellation, or background continuation?

A useful implementation has a reactive pending state, an error state, and a success path. Vue’s async component support also provides loading and error components, including a default delay that helps prevent a loading indicator from flashing briefly on fast connections. See Vue’s async component guidance when the loading boundary is a lazy route or component.

1. Choose the loading pattern before choosing the spinner component

Start with the information you can honestly communicate.

  • Spinner: Work is active, but the duration and percentage are unknown. This fits a save button, a single card refresh, a short lookup, or an AI response without reliable progress.
  • Progress indicator: You know the total or a meaningful partial result. This fits uploads, imports, exports, and multi-stage processing.
  • Skeleton: The structure is known before content arrives. This fits an initial list, card grid, or detail screen with a stable layout.
  • Inline status: The task should not interrupt work. This fits autosave, background sync, reconnecting, or a non-blocking refresh.

A spinner communicates activity, not speed, success, or a percentage. If a user is waiting for a large upload or multi-stage job, a spinner alone provides too little information. DOM Studio’s Progress components include linear, segmented, radial, and indeterminate patterns, so your indicator can reflect the work rather than forcing every case into a circular loader.

Visual comparison of spinner, progress bar, skeleton, and inline status loading patterns

Expected result: You can describe the waiting state in one sentence, such as “Saving this record,” “Uploading 3 of 8 files,” or “Refreshing revenue data in the background.”

Troubleshooting: If a status includes a percentage that your service cannot calculate, remove the percentage. Use a spinner with a specific status message instead.

2. Match the spinner to the smallest useful region

The scope of a spinner component should match the scope of the pending work.

For a form submission, place the spinner inside the submit button or beside a form-level status. Keep entered values visible. Disable the submit action while the same request is in flight so a double-click cannot create duplicates, but keep unrelated navigation and controls available.

For a dashboard refresh, show the spinner in the card header, empty state, or small status area for the panel that is loading. Keep existing data visible when it remains useful. A full-page overlay makes sense only when the route cannot be safely used before essential content is ready.

For list or detail fetching, use skeletons for an initial load when the final structure is predictable. On later refreshes, favor a compact spinner or a “Refreshing” message near the affected region. Replacing readable data with blank placeholders introduces interruption without adding clarity.

For an AI-generated response, show an inline spinner and a direct status such as “Generating summary.” Preserve the original prompt and any earlier output. If the request continues after users leave the screen, make its status persistent in a job panel or notification center.

Expected result: Users can still read, navigate, and work in every region unaffected by the request.

Troubleshooting: If a single card refresh has a page-wide overlay, move the pending state down to the card. If the entire route really depends on the request, explain that dependency with a page-level label rather than animation alone.

3. Model the request as explicit Vue states

Treat loading as a small state machine, not a Boolean distributed through callbacks. At minimum, define idle, pending, success, and error. Add cancelled or timedOut if the product supports those outcomes.

For a form action, set the state to pending before calling the service. Ignore another submit while it is pending. On success, set a visible completion message. On failure, retain field values, restore the relevant action, and show a recovery path. This sequence is more reliable than turning a spinner on and off in several unrelated handlers.

Use this implementation checklist:

  1. Set pending synchronously before beginning the request.
  2. Prevent the same action from running again while it is pending.
  3. Show a status specific to the work, not merely “Loading.”
  4. Set success only after the service confirms completion.
  5. Set error when the request fails and make retry safe.
  6. Reset or transition the state only after users have received the outcome.

If you use DOM Studio, use the Spinner reference for the component implementation, then keep the request logic in the feature that owns the async operation.

Form submission interface with an inline button spinner and visible completion or retry status area

Expected result: The action changes immediately, duplicate submission is blocked, and the user receives a clear success or recovery message.

Troubleshooting: Do not leave a form permanently disabled after an error. If retry can create duplicate side effects, protect the service with an idempotency key or server-side duplicate guard as well.

4. Make the loading state understandable without animation

A spinning icon alone is not an accessible status. Every pending region needs a meaningful text equivalent.

If a spinner sits next to visible text such as “Saving profile,” treat the icon as decorative and expose the text. If the spinner is the only visible signal, give it an accessible name that describes the actual work, such as “Loading invoices.” Do not depend on motion, color, or position alone to communicate state.

When work is measurable, use a named progressbar with accurate minimum, maximum, and current values. W3C notes that changes to progressbar values are not necessarily announced as they change. A polite live status can communicate useful milestones without moving focus. See W3C’s progress-bar status technique for a practical example.

Use aria-busy on a region only while it is genuinely updating, then remove it when the new content, error state, or empty state is ready. Do not announce every animation frame or small percentage increase. Announce milestones that help a person decide whether to wait, continue working, or retry.

Test the experience four ways:

  1. Ignore animation and confirm the state is clear from text alone.
  2. Use only a keyboard while work is pending. Only a conflicting action should be unavailable.
  3. Use a screen reader and verify that start, meaningful progress, completion, and error states make sense without focus moving.
  4. Confirm success and failure do not rely only on color.

Expected result: People who cannot see the icon, prefer reduced motion, or are looking elsewhere still receive a useful explanation.

Troubleshooting: If the same message is announced repeatedly, report fewer milestones or use one stable status region instead of recreating it on every render.

5. Test real loading scenarios before shipping

Test under slow networks and error conditions, not only a fast local happy path.

Dashboard refresh

Keep existing data visible, refresh one panel, and show a compact labeled spinner in that panel. Verify that other cards, filters, and navigation remain usable. Simulate an error and provide a retry action in the affected region.

Form submission

Submit once and verify that the primary action prevents a duplicate request. Keep validation errors and field values visible. Confirm that completion is announced and users know whether they remain on the screen or are moving elsewhere.

Initial page load

Use skeletons only where the final layout is predictable. Use a page-level spinner only when essential content prevents safe interaction. Test slow responses, failure states, and empty results.

Background and AI work

Allow users to continue unrelated tasks. Show a persistent, specific status such as “Syncing files” or “Generating report.” Provide the final result, error, or retry control where users can find it later.

For an independent visual walkthrough, watch this loading spinner and progress bar accessibility video.

6. Standardize the pattern in your component system

A spinner component can standardize size, contrast, labels, and placement. It cannot decide scope, prevent duplicate actions, or communicate failure by itself. Those behaviors belong to the request state and the surrounding interface.

We recommend a small team convention:

  • A spinner means active work with unknown duration.
  • A progress indicator means measurable work.
  • A skeleton means known layout and unknown content.
  • A status message means background or non-blocking activity.
  • Every pending action has both a success and error outcome.

This convention reduces one-off loading patterns across product surfaces. It also makes design and engineering reviews faster because everyone can ask the same questions: What is waiting? What remains usable? What will the user hear? What happens next?

Finish with a loading state users can trust

The best spinner component is usually the smallest one that tells the truth. Put it next to the work, label the state, disable only the action that conflicts with the request, and give people a clear completion or recovery path.

Start with one high-traffic form or dashboard card, test it under slow and failed requests, then apply the same convention across the application. When you need a ready-to-integrate loader, explore the DOM Studio Spinner component. For measurable completion, pair it with the DOM Studio Progress components.