Skip to main content
The <VueListInitialLoader> component shows a loading indicator only during the very first data fetch. Once data has loaded successfully, it never shows again (even during subsequent page changes or filtering).

Basic usage

<template>
  <VueList endpoint="users">
    <VueListInitialLoader>
      <div>Loading users...</div>
    </VueListInitialLoader>
    
    <VueListItems #default="{ items }">
      <div v-for="user in items" :key="user.id">
        {{ user.name }}
      </div>
    </VueListItems>
  </VueList>
</template>

Props

This component has no props. It uses Vue’s inject to access the list state from the parent <VueList> component.

Behavior

  • Shows when: isInitialLoading === true (only during first fetch)
  • Hidden after: First successful data load
  • Never shows again: Even when paginating, filtering, or sorting
  • Hides <VueListItems>: Items won’t render until initial load completes

Slot

The default slot renders your custom loading UI:
<VueListInitialLoader>
  <div class="loading-screen">
    <div class="spinner"></div>
    <p>Loading your data...</p>
  </div>
</VueListInitialLoader>

Examples

Skeleton loader

<VueListInitialLoader>
  <div class="space-y-4">
    <div class="skeleton-item" v-for="i in 5" :key="i">
      <div class="skeleton-avatar"></div>
      <div class="skeleton-text">
        <div class="skeleton-line w-3/4"></div>
        <div class="skeleton-line w-1/2"></div>
      </div>
    </div>
  </div>
</VueListInitialLoader>

<style>
.skeleton-line {
  height: 12px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 4px;
  margin-bottom: 8px;
}

.skeleton-avatar {
  width: 48px;
  height: 48px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 50%;
}

@keyframes loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>

Centered spinner

<VueListInitialLoader>
  <div class="flex items-center justify-center py-16">
    <div class="text-center">
      <svg class="animate-spin h-12 w-12 text-blue-500 mx-auto" fill="none" viewBox="0 0 24 24">
        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
      </svg>
      <p class="mt-4 text-gray-600">Loading products...</p>
    </div>
  </div>
</VueListInitialLoader>

Skeleton grid for products

<VueListInitialLoader>
  <div class="grid grid-cols-3 gap-4">
    <div class="skeleton-card" v-for="i in 9" :key="i">
      <div class="skeleton-image"></div>
      <div class="skeleton-line w-full"></div>
      <div class="skeleton-line w-2/3"></div>
      <div class="skeleton-line w-1/2"></div>
    </div>
  </div>
</VueListInitialLoader>

<style>
.skeleton-card {
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}

.skeleton-image {
  height: 200px;
  margin-bottom: 12px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 4px;
}

.skeleton-line {
  height: 12px;
  margin-bottom: 8px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 4px;
}

@keyframes loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>

Skeleton table

<table class="w-full">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <VueListInitialLoader>
      <tr v-for="i in 10" :key="i">
        <td><div class="skeleton-line w-32"></div></td>
        <td><div class="skeleton-line w-48"></div></td>
        <td><div class="skeleton-line w-20"></div></td>
      </tr>
    </VueListInitialLoader>
    
    <VueListItems>
      <template #item="{ item }">
        <tr>
          <td>{{ item.name }}</td>
          <td>{{ item.email }}</td>
          <td>{{ item.status }}</td>
        </tr>
      </template>
    </VueListItems>
  </tbody>
</table>

Pulsing dots

<VueListInitialLoader>
  <div class="flex items-center justify-center py-16">
    <div class="flex gap-2">
      <div class="w-3 h-3 bg-blue-500 rounded-full animate-bounce"></div>
      <div class="w-3 h-3 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
      <div class="w-3 h-3 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.4s"></div>
    </div>
  </div>
</VueListInitialLoader>

Full-screen loader

<VueListInitialLoader>
  <div class="fixed inset-0 bg-white flex items-center justify-center">
    <div class="text-center">
      <svg class="animate-spin h-16 w-16 text-blue-500 mx-auto" viewBox="0 0 24 24">
        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
      </svg>
      <h2 class="mt-4 text-xl font-semibold">Loading Dashboard</h2>
      <p class="mt-2 text-gray-600">Please wait...</p>
    </div>
  </div>
</VueListInitialLoader>

Difference from Loader

FeatureVueListInitialLoaderVueListLoader
When it showsOnly during first loadDuring subsequent loads
Hides items?Yes (items are hidden)No (items remain visible)
Use caseInitial data fetchPage changes, filtering, sorting
Typical UIFull skeleton or placeholderSubtle overlay or indicator

Complete example

<template>
  <VueList endpoint="posts" :per-page="10">
    <!-- Initial load: Show skeleton -->
    <VueListInitialLoader>
      <div class="space-y-6">
        <div class="post-skeleton" v-for="i in 5" :key="i">
          <div class="skeleton-line w-3/4 h-6"></div>
          <div class="skeleton-line w-full h-4 mt-2"></div>
          <div class="skeleton-line w-full h-4"></div>
          <div class="skeleton-line w-2/3 h-4"></div>
          <div class="flex gap-2 mt-4">
            <div class="skeleton-line w-20 h-8"></div>
            <div class="skeleton-line w-20 h-8"></div>
          </div>
        </div>
      </div>
    </VueListInitialLoader>

    <!-- After initial load: Show items -->
    <VueListItems #default="{ items }">
      <div class="space-y-6">
        <article v-for="post in items" :key="post.id" class="post">
          <h2>{{ post.title }}</h2>
          <p>{{ post.excerpt }}</p>
          <div class="flex gap-2">
            <span class="badge">{{ post.category }}</span>
            <span class="text-sm text-gray-500">{{ post.date }}</span>
          </div>
        </article>
      </div>
    </VueListItems>

    <!-- Subsequent loads: Show subtle loader -->
    <VueListLoader>
      <div class="text-center py-4">
        <span class="text-sm text-gray-500 animate-pulse">Loading...</span>
      </div>
    </VueListLoader>

    <VueListPagination class="mt-8" />
  </VueList>
</template>
Use skeleton loaders that match your actual content layout for a smoother perceived loading experience.
The <VueListItems> component is automatically hidden while isInitialLoading is true, so you don’t need to manually control visibility.

Next steps

Loader

Loading state for subsequent fetches

Empty state

Handle empty results

Build docs developers (and LLMs) love