vue-loading-overlay vs vue-spinner
Loading Indicators in Vue.js Applications
vue-loading-overlayvue-spinner
Loading Indicators in Vue.js Applications

vue-loading-overlay and vue-spinner are both Vue.js libraries designed to provide visual feedback during asynchronous operations, but they serve different use cases and offer distinct implementation models. vue-loading-overlay renders a full-screen or container-scoped semi-transparent overlay with a centered spinner, effectively blocking user interaction while indicating loading activity. In contrast, vue-spinner is a collection of lightweight, standalone animated spinner components that can be embedded directly into UI elements without blocking interaction. While both aim to improve user experience during data fetching or processing, their architectural approaches — modal blocking vs. inline decoration — lead to different integration patterns and user interface behaviors.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
vue-loading-overlay58,1541,27057.5 kB4a year agoMIT
vue-spinner24,8181,810-206 years agoMIT

vue-loading-overlay vs vue-spinner: Choosing the Right Loading Pattern in Vue

Both vue-loading-overlay and vue-spinner help communicate loading states in Vue applications, but they solve fundamentally different problems. One creates blocking overlays, the other provides decorative spinners. Understanding this distinction is key to choosing the right tool.

🛑 Blocking vs Non-Blocking: Core UX Difference

vue-loading-overlay shows a semi-transparent layer that covers part or all of the screen, disabling clicks underneath. This is useful when you must prevent user interaction.

<!-- vue-loading-overlay: blocks interaction -->
<template>
  <div>
    <loading :active="isLoading" :is-full-page="true" />
    <button @click="submitForm">Submit</button>
  </div>
</template>

<script>
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/css/vue-loading.css';

export default {
  components: { Loading },
  data() {
    return { isLoading: false };
  },
  methods: {
    async submitForm() {
      this.isLoading = true;
      await api.submit();
      this.isLoading = false;
    }
  }
};
</script>

vue-spinner renders only the animation itself — no overlay, no pointer events blocking. Users can keep interacting with the page.

<!-- vue-spinner: purely visual -->
<template>
  <div>
    <clip-loader v-if="isLoading" color="#36d7b7" size="20px" />
    <button @click="fetchData">Refresh</button>
  </div>
</template>

<script>
import { ClipLoader } from 'vue-spinner';

export default {
  components: { ClipLoader },
  data() {
    return { isLoading: false };
  },
  methods: {
    async fetchData() {
      this.isLoading = true;
      await api.getData();
      this.isLoading = false;
    }
  }
};
</script>

💡 Rule of thumb: If you’re disabling a button and showing a spinner, you probably don’t need an overlay. If you’re preventing navigation or form edits during a save, use an overlay.

🧩 Integration Model: Component vs Utility

vue-loading-overlay is typically used as a managed component whose visibility is controlled by a boolean prop (active). It handles DOM positioning, z-index stacking, and accessibility attributes automatically.

// Can also be used programmatically via plugin
import Loading from 'vue-loading-overlay';

Vue.use(Loading);

// Then in component:
this.$loading.show();
setTimeout(() => this.$loading.hide(), 2000);

vue-spinner exports individual spinner components (e.g., ScaleLoader, PulseLoader) that you drop into templates like any other presentational element. There’s no global state or plugin system — just props for color, size, and speed.

<template>
  <div class="card">
    <h3>User Profile</h3>
    <scale-loader v-if="profileLoading" color="#42b983" />
    <user-details v-else :user="user" />
  </div>
</template>

<script>
import { ScaleLoader } from 'vue-spinner';
export default { components: { ScaleLoader } };
</script>

🎨 Customization and Styling

vue-loading-overlay allows customization of the overlay background, spinner type, and text via props:

<loading
  :active="isLoading"
  background-color="rgba(0,0,0,0.8)"
  loader="dots"
  :text="'Processing...'"
/>

It supports built-in loaders like spinner, dots, and bars, but you cannot easily inject custom SVG animations.

vue-spinner gives you fine control over animation appearance through CSS-compatible props:

<rotate-loader
  :loading="isLoading"
  :color="'#ff6b6b'"
  :size="'15px'"
  :margin="'2px'"
/>

Each spinner is a self-contained component with its own animation logic, making it easy to match brand colors or adjust sizing without affecting global styles.

📱 Responsive and Accessibility Considerations

vue-loading-overlay automatically sets aria-busy="true" on the overlay and manages focus trapping in full-page mode, which helps screen reader users understand the page is temporarily unavailable.

vue-spinner components do not include ARIA attributes by default. You must add them manually if needed:

<clip-loader
  v-if="loading"
  aria-label="Loading user data"
  role="status"
/>

This reflects its philosophy: it’s a visual primitive, not a complete UX solution.

🔄 Real-World Usage Scenarios

Use vue-loading-overlay when:

  • Submitting a payment form (block retries)
  • Performing a bulk data import
  • Showing a global app-level loading state during route transitions

Use vue-spinner when:

  • Loading content inside a dashboard widget
  • Showing a spinner inside a search input during typeahead
  • Indicating image lazy-loading in a gallery

⚠️ Maintenance Status

As of 2024, both packages are actively maintained with recent releases supporting Vue 3 (via compatibility builds or dedicated versions). Neither is deprecated, and both provide clear migration paths for Vue 2 → Vue 3 projects.

📊 Summary Table

Featurevue-loading-overlayvue-spinner
Blocks interaction✅ Yes (via overlay)❌ No
Use caseModal-like loadingInline decorative spinners
API styleState-driven component or pluginStateless presentational components
Custom animations❌ Limited to built-in types✅ Via props (color, size, speed)
Accessibility✅ Built-in ARIA support❌ Manual implementation required
Bundle impactLarger (includes overlay logic)Smaller (only spinner SVG/CSS)

💡 Final Recommendation

Don’t think of these as competitors — they’re complementary tools. Many mature Vue apps use both: vue-spinner for inline micro-interactions and vue-loading-overlay for critical, blocking operations. Choose based on whether you need to prevent user action (overlay) or simply communicate activity (spinner).

How to Choose: vue-loading-overlay vs vue-spinner
  • vue-loading-overlay:

    Choose vue-loading-overlay when you need to prevent user interaction during critical background operations (e.g., form submission, data sync) and want a consistent, full-screen or container-level loading state. It’s ideal for scenarios where you must ensure users don’t trigger additional actions while waiting, and you prefer a declarative API that ties loading visibility directly to component state or Vuex stores. Its overlay-based approach works well in admin panels, checkout flows, or any context requiring modal-like loading behavior.

  • vue-spinner:

    Choose vue-spinner when you need lightweight, non-blocking spinners that integrate seamlessly into buttons, cards, or inline elements without disrupting the layout or user flow. It’s best suited for progressive disclosure patterns — like lazy-loaded sections or async search results — where multiple independent loading states coexist. Since it provides pure presentational components with no DOM manipulation or z-index management, it pairs well with custom UI systems that already handle loading logic externally.

README for vue-loading-overlay

Vue Loading Overlay Component

downloads jsdelivr npm-version github-tag build license TypeScript

Vue.js component for full screen loading indicator

Demo or JSFiddle

Version matrix

Vue.js versionPackage versionBranch
2.x3.x3.x
3.x6.xmain

Installation

npm install vue-loading-overlay@^6.0 

Usage

As component


<template>
    <div class="vl-parent">
        <loading v-model:active="isLoading"
                 :can-cancel="true"
                 :on-cancel="onCancel"
                 :is-full-page="fullPage"/>

        <label><input type="checkbox" v-model="fullPage">Full page?</label>
        <button @click.prevent="doAjax">fetch Data</button>
    </div>
</template>

<script>
    import Loading from 'vue-loading-overlay';
    import 'vue-loading-overlay/dist/css/index.css';

    export default {
        data() {
            return {
                isLoading: false,
                fullPage: true
            }
        },
        components: {
            Loading
        },
        methods: {
            doAjax() {
                this.isLoading = true;
                // simulate AJAX
                setTimeout(() => {
                    this.isLoading = false
                }, 5000)
            },
            onCancel() {
                console.log('User cancelled the loader.')
            }
        }
    }
</script>

As plugin

Initialise the plugin in your app

import {createApp} from 'vue';
import {LoadingPlugin} from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/css/index.css';
// Your app initialization logic goes here
const app = createApp({});
app.use(LoadingPlugin);
app.mount('#app');

Then use the plugin in your components


<template>
    <form @submit.prevent="submit"
          class="vl-parent"
          ref="formContainer">
        <!-- your form inputs goes here-->
        <label><input type="checkbox" v-model="fullPage">Full page?</label>
        <button type="submit">Login</button>
    </form>
</template>

<script>
    export default {
        data() {
            return {
                fullPage: false
            }
        },
        methods: {
            submit() {
                let loader = this.$loading.show({
                    // Optional parameters
                    container: this.fullPage ? null : this.$refs.formContainer,
                    canCancel: true,
                    onCancel: this.onCancel,
                });
                // simulate AJAX
                setTimeout(() => {
                    loader.hide()
                }, 5000)
            },
            onCancel() {
                console.log('User cancelled the loader.')
            }
        }
    }
</script>

or same with Composition API

<script setup>
    import {ref, inject} from 'vue'
    import {useLoading} from 'vue-loading-overlay'
    
    const $loading = useLoading({
        // options
    });
    // or use inject without importing useLoading
    // const $loading =  inject('$loading')

    const fullPage = ref(false)

    const submit = () => {
        const loader = $loading.show({
            // Optional parameters
        });
        // simulate AJAX
        setTimeout(() => {
            loader.hide()
        }, 5000)
    }
</script>

Available props

The component accepts these props:

AttributeTypeDefaultDescription
activeBooleanfalseShow loading by default when true, use it as v-model:active
can-cancelBooleanfalseAllow user to cancel by pressing ESC or clicking outside
on-cancelFunction()=>{}Do something upon cancel, works in conjunction with can-cancel
is-full-pageBooleantrueWhen false; limit loader to its container^
transitionStringfadeTransition name
colorString#000Customize the color of loading icon
heightNumber*Customize the height of loading icon
widthNumber*Customize the width of loading icon
loaderStringspinnerName of icon shape you want use as loader, spinner or dots or bars
background-colorString#fffCustomize the overlay background color
opacityNumber0.5Customize the overlay background opacity
z-indexNumber9999Customize the overlay z-index
enforce-focusBooleantrueForce focus on loader
lock-scrollBooleanfalseFreeze the scrolling during full screen loader
  • ^When is-full-page is set to false, the container element should be positioned as position: relative. You can use CSS helper class vl-parent.
  • *The default height and width values may vary based on the loader prop value

Available slots

The component accepts these slots:

  • default - Replace the animated icon with yours
  • before - Place anything before the animated icon, you may need to style this.
  • after - Place anything after the animated icon, you may need to style this.

API methods

this.$loading.show(?propsData,?slots)

import {h} from 'vue';

let loader = this.$loading.show({
    // Pass props by their camelCased names
    container: this.$refs.loadingContainer,
    canCancel: true, // default false
    onCancel: this.yourCallbackMethod,
    color: '#000000',
    loader: 'spinner',
    width: 64,
    height: 64,
    backgroundColor: '#ffffff',
    opacity: 0.5,
    zIndex: 999,
}, {
    // Pass slots by their names
    default: h('your-custom-loader-component-name'),
});
// hide loader whenever you want
loader.hide();

Global configs

You can set props and slots for all future instances when using as plugin

app.use(LoadingPlugin, {
    // props
    color: 'red'
}, {
    // slots
})

Further you can override any prop or slot when creating new instances

let loader = this.$loading.show({
    color: 'blue'
}, {
    // override slots
});

Use directly in browser (with CDN)

<!-- Vue js -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.3"></script>
<!-- Lastly add this package -->
<script src="https://cdn.jsdelivr.net/npm/vue-loading-overlay@6"></script>
<link href="https://cdn.jsdelivr.net/npm/vue-loading-overlay@6/dist/css/index.css" rel="stylesheet">
<!-- Init the plugin and component-->
<script>
    const app = Vue.createApp({});
    app.use(VueLoading.LoadingPlugin);
    app.component('loading', VueLoading.Component)
    app.mount('#app')
</script>

Run examples on your localhost

  • Clone this repo
  • Make sure you have node-js >=20.11 and pnpm >=8.x pre-installed
  • Install dependencies pnpm install
  • Run webpack dev server npm start
  • This should open the demo page in your default web browser

Testing

  • This package is using Jest and vue-test-utils for testing.
  • Execute tests with this command npm test

License

MIT License