Documentation Index
Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows developers how to add pagination to any data table in the DeployStack frontend.
The pagination follows the shadcn-vue DataTable pattern:
Without selection (default):
[Rows per page ▼] Page 1 of 7 [<<] [<] [>] [>>]
With selection enabled:
0 of 68 row(s) selected. [Rows per page ▼] Page 1 of 7 [<<] [<] [>] [>>]
- Selection info (left, optional) - “X of Y row(s) selected.” Only shown when selection props are provided
- Rows per page selector (right) - Dropdown with options: 10, 20, 30, 40, 50 (hidden on mobile)
- Page indicator (right) - “Page X of Y”
- Navigation buttons (right) - First, Previous, Next, Last (First/Last hidden on mobile)
Quick Implementation
1. Service Layer
Add pagination support to your service:
// services/yourService.ts
export interface PaginationParams {
limit?: number
offset?: number
}
export interface PaginationMeta {
total: number
limit: number
offset: number
has_more: boolean
}
export interface PaginatedResponse<T> {
items: T[]
pagination: PaginationMeta
}
static async getItemsPaginated(
filters?: ItemFilters,
pagination?: PaginationParams
): Promise<PaginatedResponse<Item>> {
const url = new URL(`${this.baseUrl}/api/items`)
// Add filters and pagination params
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined) url.searchParams.append(key, String(value))
})
}
if (pagination) {
if (pagination.limit) url.searchParams.append('limit', String(pagination.limit))
if (pagination.offset) url.searchParams.append('offset', String(pagination.offset))
}
const response = await fetch(url.toString(), {
method: 'GET',
credentials: 'include',
headers: { 'Content-Type': 'application/json' }
})
const data = await response.json()
return {
items: data.data.items,
pagination: data.data.pagination
}
}
2. Component Implementation
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import PaginationControls from '@/components/ui/pagination/PaginationControls.vue'
import { YourService, type PaginationMeta } from '@/services/yourService'
// Pagination state
const currentPage = ref(1)
const pageSize = ref(20)
const totalItems = ref(0)
const items = ref([])
const isLoading = ref(false)
// Fetch data with pagination
async function fetchItems() {
isLoading.value = true
try {
const offset = (currentPage.value - 1) * pageSize.value
const response = await YourService.getItemsPaginated(
filters.value,
{ limit: pageSize.value, offset }
)
items.value = response.items
totalItems.value = response.pagination.total
} finally {
isLoading.value = false
}
}
// Event handlers
async function handlePageChange(page: number) {
currentPage.value = page
await fetchItems()
}
async function handlePageSizeChange(newPageSize: number) {
pageSize.value = newPageSize
currentPage.value = 1
await fetchItems()
}
onMounted(() => fetchItems())
</script>
<template>
<div class="space-y-4">
<!-- Your data table -->
<YourTableComponent :items="items" />
<!-- Pagination controls -->
<PaginationControls
v-if="totalItems > 0"
:current-page="currentPage"
:page-size="pageSize"
:total-items="totalItems"
:is-loading="isLoading"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
/>
</div>
</template>
3. Add Translations
Add to your i18n file (e.g., i18n/locales/en/yourFeature.ts):
pagination: {
rowsPerPage: 'Rows per page',
pageInfo: 'Page {current} of {total}',
firstPage: 'Go to first page',
previousPage: 'Go to previous page',
nextPage: 'Go to next page',
lastPage: 'Go to last page',
rowsSelected: '{selected} of {total} row(s) selected.' // Only needed if using selection
}
Props
| Prop | Type | Required | Default | Description |
|---|
currentPage | number | Yes | - | Current page number (1-based) |
pageSize | number | Yes | - | Items per page |
totalItems | number | Yes | - | Total number of items |
isLoading | boolean | No | false | Loading state (disables navigation) |
pageSizeOptions | number[] | No | [10, 20, 30, 40, 50] | Available page sizes |
selectedCount | number | No | - | Number of selected rows (enables selection display) |
totalRows | number | No | - | Total rows for selection display |
Events
@page-change(page: number) - Emitted when page changes
@page-size-change(pageSize: number) - Emitted when page size changes
With Row Selection
When your table has row selection enabled, pass the selectedCount and totalRows props to show the selection info on the left:
<PaginationControls
:current-page="currentPage"
:page-size="pageSize"
:total-items="totalItems"
:selected-count="selectedRows.length"
:total-rows="items.length"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
/>
shadcn-vue Components Used
The PaginationControls component uses these shadcn-vue components:
Button - For navigation buttons (icon-only, outline variant, size-8)
Label - For “Rows per page” label
Select, SelectContent, SelectItem, SelectTrigger, SelectValue - For page size selector (w-20)
- Lucide icons:
ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight
Responsive Behavior
The pagination is responsive:
| Element | Desktop | Mobile |
|---|
| Selection info (if enabled) | Visible | Visible |
| Rows per page selector | Visible | Hidden |
| Page info | Visible | Visible |
| First page button | Visible | Hidden |
| Previous page button | Visible | Visible |
| Next page button | Visible | Visible |
| Last page button | Visible | Hidden |
On mobile, the navigation buttons use ml-auto to push them to the right edge.
Backend Requirements
Your backend API must support these query parameters:
limit - Number of items per page (1-100)
offset - Number of items to skip
And return this response format:
{
"success": true,
"data": {
"items": [...],
"pagination": {
"total": 150,
"limit": 20,
"offset": 40,
"has_more": true
}
}
}