Overview
VueList is a headless component library that handles the data layer of your lists while giving you complete control over the UI. It manages pagination, filtering, sorting, search, and API calls — you focus on rendering.
Quick Setup
Install VueList
First, install the package in your Vue 3 project: npm install @7span/vue-list
Configure the Plugin
Set up VueList globally with a requestHandler function. This function tells VueList how to fetch data from your API. import { createApp } from 'vue'
import VueList from '@7span/vue-list'
import axios from 'axios'
import App from './App.vue'
const app = createApp ( App )
app . use ( VueList , {
requestHandler ( context ) {
const { endpoint , page , perPage , search , filters , sortBy , sortOrder } = context
return axios
. get ( `/api/ ${ endpoint } ` , {
params: {
page ,
limit: perPage ,
search ,
sort_by: sortBy ,
sort_order: sortOrder ,
... filters
}
})
. then (({ data }) => {
return {
items: data . results , // Array of items to display
count: data . total // Total number of items available
}
})
}
})
app . mount ( '#app' )
The requestHandler must return a Promise that resolves with an object containing:
items: Array of data to display
count: Total number of items (for pagination)
Create Your First List
Now you can use VueList components anywhere in your app: < template >
< VueList endpoint = "users" : per-page = " 10 " >
< VueListInitialLoader />
< VueListError />
< VueListItems >
< template # item = " { item } " >
< div class = "user-card" >
< h3 > {{ item . name }} </ h3 >
< p > {{ item . email }} </ p >
</ div >
</ template >
</ VueListItems >
< VueListPagination />
</ VueList >
</ template >
Core Components
VueList provides several components that work together:
VueList (Root)
The root component that manages all state and provides context to child components.
< VueList
endpoint = "products"
: per-page = " 25 "
: page = " 1 "
pagination-mode = "pagination"
>
<!-- Child components go here -->
</ VueList >
Key Props:
endpoint (required) - Identifier passed to your requestHandler
per-page - Number of items per page (default: 25)
page - Initial page number (default: 1)
pagination-mode - Either "pagination" or "loadMore"
VueListItems
Renders the list items. Automatically hidden during initial load.
< VueListItems >
<template #item="{ item, index }">
<div class="product">
<span>{{ index + 1 }}. {{ item.name }}</span>
<span>${{ item.price }}</span>
</div>
</template>
</ VueListItems >
VueListInitialLoader
Shows a loading state only on the first data fetch.
< VueListInitialLoader >
<div class="spinner">Loading...</div>
</ VueListInitialLoader >
VueListError
Displays errors from failed API requests.
< VueListError v-slot = " { error } " >
<div class="error-message">
<p>{{ error.message }}</p>
</div>
</ VueListError >
VueListEmpty
Shows when the list has no items (and is not loading or in error state).
< VueListEmpty >
<p>No users found.</p>
</ VueListEmpty >
Complete Example
Here’s a fully working example with all the essential components:
< template >
< div class = "products-page" >
< VueList
endpoint = "products"
: per-page = " 12 "
pagination-mode = "pagination"
>
< div class = "products-header" >
< h1 > Products </ h1 >
< VueListSummary v-slot = " { from , to , count } " >
< span > Showing {{ from }}-{{ to }} of {{ count }} products </ span >
</ VueListSummary >
</ div >
<!-- Loading state on first load -->
< VueListInitialLoader >
< div class = "loading-spinner" >
< p > Loading products... </ p >
</ div >
</ VueListInitialLoader >
<!-- Error state -->
< VueListError v-slot = " { error } " >
< div class = "error-banner" >
< p > Failed to load products: {{ error . message }} </ p >
</ div >
</ VueListError >
<!-- The actual list -->
< VueListItems >
< template # default = " { items } " >
< div class = "products-grid" >
< div
v-for = " product in items "
: key = " product . id "
class = "product-card"
>
< img : src = " product . image " : alt = " product . name " />
< h3 > {{ product . name }} </ h3 >
< p class = "price" > ${{ product . price }} </ p >
< button > Add to Cart </ button >
</ div >
</ div >
</ template >
</ VueListItems >
<!-- Empty state -->
< VueListEmpty >
< div class = "empty-state" >
< p > No products found </ p >
</ div >
</ VueListEmpty >
<!-- Pagination -->
< VueListPagination />
</ VueList >
</ div >
</ template >
< style scoped >
.products-grid {
display : grid ;
grid-template-columns : repeat ( auto-fill , minmax ( 250 px , 1 fr ));
gap : 1.5 rem ;
}
.product-card {
border : 1 px solid #e5e7eb ;
border-radius : 8 px ;
padding : 1 rem ;
}
.loading-spinner ,
.error-banner ,
.empty-state {
padding : 2 rem ;
text-align : center ;
}
</ style >
Accessing List State
You can access the full list state using the default slot on <VueList>:
< VueList endpoint = "users" v-slot = " { items , count , isLoading , error , refresh } " >
<div>
<button @click="refresh()" :disabled="isLoading">
Refresh List
</button>
<p v-if="isLoading">Loading...</p>
<p v-else>Total: {{ count }} users</p>
<div v-for="user in items" :key="user.id">
{{ user.name }}
</div>
</div>
</ VueList >
Available slot props:
items - Array of current page items
count - Total number of items
isLoading - Whether data is being fetched
isInitialLoading - Whether this is the first load
error - Error object if request failed
isEmpty - Whether items array is empty
response - Full API response object
refresh() - Function to manually refetch data
Most of the time you won’t need to use the scoped slot on <VueList> directly. The child components like <VueListItems>, <VueListError>, etc. automatically access the state they need through Vue’s provide/inject.
Next Steps
Pagination Modes Learn about pagination vs load-more modes
Filtering & Sorting Add filters and sorting to your lists
State Persistence Persist list state across page refreshes
Custom Styling Style components with slots and custom markup