Skip to main content

Overview

VueList supports two pagination modes:
  1. Pagination - Traditional page-based navigation with page numbers
  2. Load More - Infinite scroll pattern with a “Load More” button
You can switch between modes using the pagination-mode prop on the <VueList> component.

Pagination Mode

The default mode displays traditional pagination with page numbers, first/last buttons, and prev/next navigation.

Basic Usage

<template>
  <VueList 
    endpoint="articles" 
    :per-page="10"
    pagination-mode="pagination"
  >
    <VueListItems>
      <template #item="{ item }">
        <article>
          <h2>{{ item.title }}</h2>
          <p>{{ item.excerpt }}</p>
        </article>
      </template>
    </VueListItems>
    
    <VueListPagination />
  </VueList>
</template>

Customizing Pagination UI

The <VueListPagination> component provides several slots for customization:
<VueListPagination />
Renders basic First, Prev, page numbers, Next, Last buttons.

Pagination Props

The <VueListPagination> component accepts:
  • page-links (Number, default: 5) - Number of page number buttons to display
<VueListPagination :page-links="7" />

Pagination Slot Props

When using the default slot, you have access to:
PropertyTypeDescription
pageNumberCurrent page number
perPageNumberItems per page
countNumberTotal number of items
pagesCountNumberTotal number of pages
pagesToDisplayArrayArray of page numbers to show
hasNextBooleanWhether there’s a next page
hasPrevBooleanWhether there’s a previous page
prev()FunctionGo to previous page
next()FunctionGo to next page
first()FunctionGo to first page
last()FunctionGo to last page
setPage(n)FunctionGo to specific page

Load More Mode

The “Load More” mode appends new items to the existing list instead of replacing them. Perfect for infinite scroll patterns.

Basic Usage

<template>
  <VueList 
    endpoint="posts" 
    :per-page="20"
    pagination-mode="loadMore"
  >
    <VueListItems>
      <template #item="{ item }">
        <div class="post">
          <h3>{{ item.title }}</h3>
          <p>{{ item.body }}</p>
        </div>
      </template>
    </VueListItems>
    
    <VueListLoadMore />
  </VueList>
</template>
In loadMore mode, the page always resets to 1 when filters or search change. Each “Load More” click increments the internal page counter and appends items to the list.

Customizing Load More Button

You can customize the button and end-of-list message:
<VueListLoadMore v-slot="{ loadMore, hasMoreItems, isLoading }">
  <div class="load-more-container">
    <button 
      v-if="hasMoreItems" 
      @click="loadMore"
      :disabled="isLoading"
      class="load-more-btn"
    >
      <span v-if="isLoading">Loading...</span>
      <span v-else>Load More Posts</span>
    </button>
    
    <p v-else class="end-message">
      You've reached the end!
    </p>
  </div>
</VueListLoadMore>

Load More Slot Props

PropertyTypeDescription
isLoadingBooleanWhether data is currently being fetched
hasMoreItemsBooleanWhether more items are available
loadMore()FunctionFetch and append next page of items

Infinite Scroll Implementation

You can implement true infinite scroll by triggering loadMore() when the user scrolls near the bottom:
<template>
  <VueList 
    endpoint="feed" 
    :per-page="30"
    pagination-mode="loadMore"
  >
    <div ref="scrollContainer" @scroll="handleScroll" class="feed-container">
      <VueListItems>
        <template #item="{ item }">
          <div class="feed-item">
            {{ item.content }}
          </div>
        </template>
      </VueListItems>
      
      <VueListLoadMore v-slot="{ loadMore, hasMoreItems, isLoading }">
        <div ref="loadMoreTrigger">
          <div v-if="hasMoreItems && isLoading" class="loading">
            Loading more items...
          </div>
          <div v-else-if="!hasMoreItems" class="end">
            No more items
          </div>
        </div>
      </VueListLoadMore>
    </div>
  </VueList>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const scrollContainer = ref(null)
const loadMoreTrigger = ref(null)
let observer = null

onMounted(() => {
  // Use Intersection Observer for better performance
  observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          // Trigger load more when trigger element is visible
          const loadMoreBtn = entry.target.querySelector('button')
          if (loadMoreBtn) loadMoreBtn.click()
        }
      })
    },
    {
      root: scrollContainer.value,
      threshold: 0.1
    }
  )
  
  if (loadMoreTrigger.value) {
    observer.observe(loadMoreTrigger.value)
  }
})

onUnmounted(() => {
  if (observer) observer.disconnect()
})
</script>

URL History

By default, VueList adds the current page number to the URL query params (e.g., ?page=3). This is useful for:
  • Sharing links to specific pages
  • Browser back/forward navigation
  • Bookmarking

Disable URL History

You can disable this behavior:
<VueList 
  endpoint="products"
  pagination-mode="pagination"
  :has-pagination-history="false"
>
  <!-- ... -->
</VueList>
URL history is automatically disabled in loadMore mode since it doesn’t make sense to bookmark a partially loaded infinite list.

Events

VueList emits events when pagination changes:
<template>
  <VueList 
    endpoint="products"
    pagination-mode="pagination"
    @after-page-change="onPageChange"
  >
    <!-- ... -->
  </VueList>
</template>

<script setup>
function onPageChange(response) {
  console.log('Page changed, got', response.items.length, 'items')
  console.log('Total count:', response.count)
}
</script>
For load more mode:
<template>
  <VueList 
    endpoint="posts"
    pagination-mode="loadMore"
    @after-load-more="onLoadMore"
  >
    <!-- ... -->
  </VueList>
</template>

<script setup>
function onLoadMore(response) {
  console.log('Loaded more items:', response.items.length)
}
</script>

Complete Example: Switchable Modes

Here’s an example that lets users switch between pagination modes:
<template>
  <div>
    <div class="mode-toggle">
      <button 
        @click="mode = 'pagination'" 
        :class="{ active: mode === 'pagination' }"
      >
        Pages
      </button>
      <button 
        @click="mode = 'loadMore'" 
        :class="{ active: mode === 'loadMore' }"
      >
        Load More
      </button>
    </div>

    <VueList 
      endpoint="products" 
      :per-page="12"
      :pagination-mode="mode"
      :key="mode"
    >
      <VueListItems>
        <template #item="{ item }">
          <div class="product-card">
            <h3>{{ item.name }}</h3>
            <p>${{ item.price }}</p>
          </div>
        </template>
      </VueListItems>
      
      <VueListPagination v-if="mode === 'pagination'" />
      <VueListLoadMore v-else />
    </VueList>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const mode = ref('pagination')
</script>
Use the :key attribute when switching modes to force VueList to reset its internal state. This prevents issues with accumulated items from load-more mode appearing when switching back to pagination.

Build docs developers (and LLMs) love