Documentation
Pagination
Pagination helps manage large datasets by dividing the table data into manageable pages. This improves performance, reduces visual clutter, and provides a better user experience for navigating through extensive data collections.
1import { useState } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme } from "@simple-table/react";3import { paginationConfig, paginationData, PAGINATION_ROWS_PER_PAGE } from "./pagination.demo-data";4import "@simple-table/react/styles.css";56const PaginationDemo = ({7 height,8 theme,9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [rows, setRows] = useState(paginationData.slice(0, PAGINATION_ROWS_PER_PAGE));14 const [isLoading, setIsLoading] = useState(false);1516 const onNextPage = async (pageIndex: number) => {17 const startIndex = pageIndex * PAGINATION_ROWS_PER_PAGE;18 const endIndex = startIndex + PAGINATION_ROWS_PER_PAGE;1920 setIsLoading(true);21 await new Promise((resolve) => setTimeout(resolve, 800));22 const newPageData = paginationData.slice(startIndex, endIndex);2324 if (newPageData.length === 0 || rows.length > startIndex) {25 setIsLoading(false);26 return false;27 }2829 setRows((prev) => [...prev, ...newPageData]);30 setIsLoading(false);31 return true;32 };3334 return (35 <SimpleTable36 defaultHeaders={paginationConfig.headers}37 height={height ?? "auto"}38 isLoading={isLoading}39 onNextPage={onNextPage}40 rows={rows}41 rowsPerPage={PAGINATION_ROWS_PER_PAGE}42 shouldPaginate43 theme={theme}44 />45 );46};4748export default PaginationDemo;
1<template>2 <SimpleTable3 :default-headers="paginationConfig.headers"4 :height="height ?? 'auto'"5 :is-loading="isLoading"6 :on-next-page="onNextPage"7 :rows="rows"8 :rows-per-page="PAGINATION_ROWS_PER_PAGE"9 :should-paginate="true"10 :theme="theme"11 />12</template>1314<script setup lang="ts">15import { ref } from "vue";16import {SimpleTable} from "@simple-table/vue";import type { Theme } from "@simple-table/vue";17import { paginationConfig, paginationData, PAGINATION_ROWS_PER_PAGE } from "./pagination.demo-data";18import "@simple-table/vue/styles.css";1920const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {});2122const rows = ref(paginationData.slice(0, PAGINATION_ROWS_PER_PAGE));23const isLoading = ref(false);2425const onNextPage = async (pageIndex: number) => {26 const startIndex = pageIndex * PAGINATION_ROWS_PER_PAGE;27 const endIndex = startIndex + PAGINATION_ROWS_PER_PAGE;2829 isLoading.value = true;30 await new Promise((resolve) => setTimeout(resolve, 800));31 const newPageData = paginationData.slice(startIndex, endIndex);3233 if (newPageData.length === 0 || rows.value.length > startIndex) {34 isLoading.value = false;35 return false;36 }3738 rows.value = [...rows.value, ...newPageData];39 isLoading.value = false;40 return true;41};42</script>
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularHeaderObject, Row, Theme } from "@simple-table/angular";3import { paginationConfig, paginationData, PAGINATION_ROWS_PER_PAGE } from "./pagination.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "pagination-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="rows"13 [defaultHeaders]="headers"14 [height]="height"15 [theme]="theme"16 [shouldPaginate]="true"17 [rowsPerPage]="rowsPerPage"18 [isLoading]="isLoading"19 [onNextPage]="onNextPage"20 ></simple-table>21 `,22})23export class PaginationDemoComponent {24 @Input() height: string | number = "auto";25 @Input() theme?: Theme;2627 readonly headers: AngularHeaderObject[] = paginationConfig.headers;28 readonly rowsPerPage = PAGINATION_ROWS_PER_PAGE;29 rows: Row[] = paginationData.slice(0, PAGINATION_ROWS_PER_PAGE);30 isLoading = false;3132 onNextPage = async (pageIndex: number): Promise<boolean> => {33 const startIndex = pageIndex * PAGINATION_ROWS_PER_PAGE;34 const endIndex = startIndex + PAGINATION_ROWS_PER_PAGE;3536 this.isLoading = true;37 await new Promise((resolve) => setTimeout(resolve, 800));38 const newPageData = paginationData.slice(startIndex, endIndex);3940 if (newPageData.length === 0 || this.rows.length > startIndex) {41 this.isLoading = false;42 return false;43 }4445 this.rows = [...this.rows, ...newPageData];46 this.isLoading = false;47 return true;48 };49}505152// pagination.demo-data.ts53// Self-contained demo table setup for this example.54import type { AngularHeaderObject, Row } from "@simple-table/angular";555657export const PAGINATION_ROWS_PER_PAGE = 9;5859export const paginationHeaders: AngularHeaderObject[] = [60 { accessor: "id", label: "ID", width: 60, type: "number" },61 { accessor: "name", label: "Name", width: "1fr", minWidth: 100, type: "string" },62 { accessor: "email", label: "Email", width: 200, type: "string" },63 { accessor: "role", label: "Role", width: 140, type: "string" },64 { accessor: "department", label: "Department", width: 150, type: "string" },65 { accessor: "status", label: "Status", width: 110, type: "string" },66];6768export const paginationData: Row[] = [69 { id: 1, name: "Miguel Santos", email: "miguel.santos@grandhotel.com", role: "Guest Relations Manager", department: "Front Office", status: "Active" },70 { id: 2, name: "Carmen Delacroix", email: "carmen.d@grandhotel.com", role: "Head Concierge", department: "Concierge", status: "Active" },71 { id: 3, name: "Dimitri Petrov", email: "dimitri.p@grandhotel.com", role: "Executive Chef", department: "Culinary", status: "Active" },72 { id: 4, name: "Priya Sharma", email: "priya.sharma@grandhotel.com", role: "Spa Director", department: "Wellness", status: "On Leave" },73 { id: 5, name: "Giovanni Rossi", email: "giovanni.r@grandhotel.com", role: "Banquet Manager", department: "Events", status: "Active" },74 { id: 6, name: "Anastasia Volkov", email: "anastasia.v@grandhotel.com", role: "Housekeeping Supervisor", department: "Housekeeping", status: "Active" },75 { id: 7, name: "Omar Hassan", email: "omar.hassan@grandhotel.com", role: "Night Auditor", department: "Front Office", status: "Active" },76 { id: 8, name: "Lucia Fernandez", email: "lucia.f@grandhotel.com", role: "Restaurant Manager", department: "Food & Beverage", status: "Active" },77 { id: 9, name: "Kenji Nakamura", email: "kenji.n@grandhotel.com", role: "Guest Services Coordinator", department: "Guest Services", status: "Active" },78 { id: 10, name: "Victoria Sterling", email: "victoria.s@grandhotel.com", role: "Sales Director", department: "Sales & Marketing", status: "Active" },79 { id: 11, name: "Rafael Martinez", email: "rafael.m@grandhotel.com", role: "Security Chief", department: "Security", status: "Active" },80 { id: 12, name: "Ingrid Larsson", email: "ingrid.l@grandhotel.com", role: "Event Coordinator", department: "Events", status: "Active" },81 { id: 13, name: "Hassan Al-Rashid", email: "hassan.a@grandhotel.com", role: "Maintenance Supervisor", department: "Engineering", status: "Active" },82 { id: 14, name: "Chloe Bennett", email: "chloe.b@grandhotel.com", role: "Front Desk Agent", department: "Front Office", status: "Active" },83 { id: 15, name: "Akira Tanaka", email: "akira.t@grandhotel.com", role: "Sous Chef", department: "Culinary", status: "Active" },84 { id: 16, name: "Isabella Costa", email: "isabella.c@grandhotel.com", role: "HR Specialist", department: "Human Resources", status: "Active" },85 { id: 17, name: "Yuki Sato", email: "yuki.sato@grandhotel.com", role: "Guest Experience Manager", department: "Guest Services", status: "Active" },86 { id: 18, name: "Marco Benedetti", email: "marco.b@grandhotel.com", role: "Sommelier", department: "Food & Beverage", status: "Active" },87 { id: 19, name: "Fatima Al-Zahra", email: "fatima.a@grandhotel.com", role: "Revenue Manager", department: "Finance", status: "Active" },88 { id: 20, name: "Sebastian Wagner", email: "sebastian.w@grandhotel.com", role: "Bell Captain", department: "Guest Services", status: "Active" },89 { id: 21, name: "Mei Lin Chen", email: "mei.chen@grandhotel.com", role: "Pastry Chef", department: "Culinary", status: "Active" },90 { id: 22, name: "Diego Morales", email: "diego.m@grandhotel.com", role: "Pool Attendant", department: "Recreation", status: "Active" },91 { id: 23, name: "Zara Khan", email: "zara.khan@grandhotel.com", role: "Business Center Manager", department: "Business Services", status: "Active" },92 { id: 24, name: "Matteo Ricci", email: "matteo.r@grandhotel.com", role: "Valet Manager", department: "Guest Services", status: "Active" },93 { id: 25, name: "Camila Gonzalez", email: "camila.g@grandhotel.com", role: "Laundry Supervisor", department: "Housekeeping", status: "Active" },94 { id: 26, name: "Bjorn Larsson", email: "bjorn.l@grandhotel.com", role: "IT Support Specialist", department: "Technology", status: "Active" },95 { id: 27, name: "Amara Okafor", email: "amara.o@grandhotel.com", role: "Training Coordinator", department: "Human Resources", status: "On Leave" },96];9798export const paginationConfig = {99 headers: paginationHeaders,100 rows: paginationData,101 tableProps: {102 rowsPerPage: PAGINATION_ROWS_PER_PAGE,103 shouldPaginate: true,104 },105} as const;106
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme } from "@simple-table/svelte";3 import { paginationConfig, paginationData, PAGINATION_ROWS_PER_PAGE } from "./pagination.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height, theme }: { height?: string | number; theme?: Theme } = $props();78 let rows = $state(paginationData.slice(0, PAGINATION_ROWS_PER_PAGE));9 let isLoading = $state(false);1011 const onNextPage = async (pageIndex: number) => {12 const startIndex = pageIndex * PAGINATION_ROWS_PER_PAGE;13 const endIndex = startIndex + PAGINATION_ROWS_PER_PAGE;1415 isLoading = true;16 await new Promise((resolve) => setTimeout(resolve, 800));17 const newPageData = paginationData.slice(startIndex, endIndex);1819 if (newPageData.length === 0 || rows.length > startIndex) {20 isLoading = false;21 return false;22 }2324 rows = [...rows, ...newPageData];25 isLoading = false;26 return true;27 };28</script>2930<SimpleTable31 defaultHeaders={paginationConfig.headers}32 height={height ?? "auto"}33 {isLoading}34 {onNextPage}35 {rows}36 rowsPerPage={PAGINATION_ROWS_PER_PAGE}37 shouldPaginate={true}38 {theme}39/>
1import { createSignal } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme } from "@simple-table/solid";3import { paginationConfig, paginationData, PAGINATION_ROWS_PER_PAGE } from "./pagination.demo-data";4import "@simple-table/solid/styles.css";56export default function PaginationDemo(props: {7 height?: string | number;8 theme?: Theme;9}) {10 const [rows, setRows] = createSignal(paginationData.slice(0, PAGINATION_ROWS_PER_PAGE));11 const [isLoading, setIsLoading] = createSignal(false);1213 const onNextPage = async (pageIndex: number) => {14 const startIndex = pageIndex * PAGINATION_ROWS_PER_PAGE;15 const endIndex = startIndex + PAGINATION_ROWS_PER_PAGE;1617 setIsLoading(true);18 await new Promise((resolve) => setTimeout(resolve, 800));19 const newPageData = paginationData.slice(startIndex, endIndex);2021 if (newPageData.length === 0 || rows().length > startIndex) {22 setIsLoading(false);23 return false;24 }2526 setRows((prev) => [...prev, ...newPageData]);27 setIsLoading(false);28 return true;29 };3031 return (32 <SimpleTable33 defaultHeaders={paginationConfig.headers}34 height={props.height ?? "auto"}35 isLoading={isLoading()}36 onNextPage={onNextPage}37 rows={rows()}38 rowsPerPage={PAGINATION_ROWS_PER_PAGE}39 shouldPaginate={true}40 theme={props.theme}41 />42 );43}
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme } from "simple-table-core";3import { paginationConfig, paginationData, PAGINATION_ROWS_PER_PAGE } from "./pagination.demo-data";4import "simple-table-core/styles.css";56export function renderPaginationDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 let rows = paginationData.slice(0, PAGINATION_ROWS_PER_PAGE);1112 const table = new SimpleTableVanilla(container, {13 defaultHeaders: paginationConfig.headers,14 rows,15 height: options?.height ?? "auto",16 theme: options?.theme,17 shouldPaginate: true,18 rowsPerPage: PAGINATION_ROWS_PER_PAGE,19 onNextPage: async (pageIndex: number) => {20 const startIndex = pageIndex * PAGINATION_ROWS_PER_PAGE;21 const endIndex = startIndex + PAGINATION_ROWS_PER_PAGE;2223 table.update({ isLoading: true });24 await new Promise((resolve) => setTimeout(resolve, 800));25 const newPageData = paginationData.slice(startIndex, endIndex);2627 if (newPageData.length === 0 || rows.length > startIndex) {28 table.update({ isLoading: false });29 return false;30 }3132 rows = [...rows, ...newPageData];33 table.update({ rows, isLoading: false });34 return true;35 },36 });3738 return table;39}404142// pagination.demo-data.ts43// Self-contained demo table setup for this example.44import type { HeaderObject, Row } from "simple-table-core";454647export const PAGINATION_ROWS_PER_PAGE = 9;4849export const paginationHeaders: HeaderObject[] = [50 { accessor: "id", label: "ID", width: 60, type: "number" },51 { accessor: "name", label: "Name", width: "1fr", minWidth: 100, type: "string" },52 { accessor: "email", label: "Email", width: 200, type: "string" },53 { accessor: "role", label: "Role", width: 140, type: "string" },54 { accessor: "department", label: "Department", width: 150, type: "string" },55 { accessor: "status", label: "Status", width: 110, type: "string" },56];5758export const paginationData: Row[] = [59 { id: 1, name: "Miguel Santos", email: "miguel.santos@grandhotel.com", role: "Guest Relations Manager", department: "Front Office", status: "Active" },60 { id: 2, name: "Carmen Delacroix", email: "carmen.d@grandhotel.com", role: "Head Concierge", department: "Concierge", status: "Active" },61 { id: 3, name: "Dimitri Petrov", email: "dimitri.p@grandhotel.com", role: "Executive Chef", department: "Culinary", status: "Active" },62 { id: 4, name: "Priya Sharma", email: "priya.sharma@grandhotel.com", role: "Spa Director", department: "Wellness", status: "On Leave" },63 { id: 5, name: "Giovanni Rossi", email: "giovanni.r@grandhotel.com", role: "Banquet Manager", department: "Events", status: "Active" },64 { id: 6, name: "Anastasia Volkov", email: "anastasia.v@grandhotel.com", role: "Housekeeping Supervisor", department: "Housekeeping", status: "Active" },65 { id: 7, name: "Omar Hassan", email: "omar.hassan@grandhotel.com", role: "Night Auditor", department: "Front Office", status: "Active" },66 { id: 8, name: "Lucia Fernandez", email: "lucia.f@grandhotel.com", role: "Restaurant Manager", department: "Food & Beverage", status: "Active" },67 { id: 9, name: "Kenji Nakamura", email: "kenji.n@grandhotel.com", role: "Guest Services Coordinator", department: "Guest Services", status: "Active" },68 { id: 10, name: "Victoria Sterling", email: "victoria.s@grandhotel.com", role: "Sales Director", department: "Sales & Marketing", status: "Active" },69 { id: 11, name: "Rafael Martinez", email: "rafael.m@grandhotel.com", role: "Security Chief", department: "Security", status: "Active" },70 { id: 12, name: "Ingrid Larsson", email: "ingrid.l@grandhotel.com", role: "Event Coordinator", department: "Events", status: "Active" },71 { id: 13, name: "Hassan Al-Rashid", email: "hassan.a@grandhotel.com", role: "Maintenance Supervisor", department: "Engineering", status: "Active" },72 { id: 14, name: "Chloe Bennett", email: "chloe.b@grandhotel.com", role: "Front Desk Agent", department: "Front Office", status: "Active" },73 { id: 15, name: "Akira Tanaka", email: "akira.t@grandhotel.com", role: "Sous Chef", department: "Culinary", status: "Active" },74 { id: 16, name: "Isabella Costa", email: "isabella.c@grandhotel.com", role: "HR Specialist", department: "Human Resources", status: "Active" },75 { id: 17, name: "Yuki Sato", email: "yuki.sato@grandhotel.com", role: "Guest Experience Manager", department: "Guest Services", status: "Active" },76 { id: 18, name: "Marco Benedetti", email: "marco.b@grandhotel.com", role: "Sommelier", department: "Food & Beverage", status: "Active" },77 { id: 19, name: "Fatima Al-Zahra", email: "fatima.a@grandhotel.com", role: "Revenue Manager", department: "Finance", status: "Active" },78 { id: 20, name: "Sebastian Wagner", email: "sebastian.w@grandhotel.com", role: "Bell Captain", department: "Guest Services", status: "Active" },79 { id: 21, name: "Mei Lin Chen", email: "mei.chen@grandhotel.com", role: "Pastry Chef", department: "Culinary", status: "Active" },80 { id: 22, name: "Diego Morales", email: "diego.m@grandhotel.com", role: "Pool Attendant", department: "Recreation", status: "Active" },81 { id: 23, name: "Zara Khan", email: "zara.khan@grandhotel.com", role: "Business Center Manager", department: "Business Services", status: "Active" },82 { id: 24, name: "Matteo Ricci", email: "matteo.r@grandhotel.com", role: "Valet Manager", department: "Guest Services", status: "Active" },83 { id: 25, name: "Camila Gonzalez", email: "camila.g@grandhotel.com", role: "Laundry Supervisor", department: "Housekeeping", status: "Active" },84 { id: 26, name: "Bjorn Larsson", email: "bjorn.l@grandhotel.com", role: "IT Support Specialist", department: "Technology", status: "Active" },85 { id: 27, name: "Amara Okafor", email: "amara.o@grandhotel.com", role: "Training Coordinator", department: "Human Resources", status: "On Leave" },86];8788export const paginationConfig = {89 headers: paginationHeaders,90 rows: paginationData,91 tableProps: {92 rowsPerPage: PAGINATION_ROWS_PER_PAGE,93 shouldPaginate: true,94 },95} as const;96
Basic Pagination
To enable pagination in Simple Table, you need to add the shouldPaginate prop to your SimpleTable component. This will automatically handle pagination of your data.
Pagination Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
shouldPaginateboolean | Optional | Enables pagination functionality for the table. When true, the table will display pagination controls and divide data into pages. | |
rowsPerPagenumber | Optional | Number of rows to display per page. Default is 10. | |
onPageChange(page: number) => void | Promise<void> | Optional | Callback function triggered when page changes, either through user interaction (clicking pagination buttons) or programmatically via tableRef.setPage(). Useful for server-side pagination to fetch data for the new page. | |
serverSidePaginationboolean | Optional | Flag to disable internal pagination slicing. When true, the table expects you to provide pre-paginated data via the rows prop. | |
totalRowCountnumber | Optional | Total number of rows available on the server (for server-side pagination). Used to calculate total pages. | |
onNextPage() => void | Optional | Callback function triggered when user clicks the next page button. | |
isLoadingboolean | Optional | Display skeleton loaders while fetching page data. Particularly useful with server-side pagination to show loading state during page transitions. |
Pagination Features
When pagination is enabled, Simple Table provides:
- Automatic page navigation controls
- Page size selection
- Current page indicator
- Total pages display
- Smart page number display with first page button - when navigating to far pages, the footer shows the first page with ellipsis (e.g., "1 ... 78 79 80") for quick access to the beginning
Server-Side Pagination
For large datasets, you can implement server-side pagination where data is fetched from the server one page at a time. This improves performance by only loading the data needed for the current page.
To enable server-side pagination, use these props:
serverSidePagination={true}- Disables internal pagination slicingtotalRowCount={number}- Total rows available on the serveronPageChange=(page) => {...}- Callback to fetch data for the new page
Pro Tip
When using server-side pagination, make sure to update your rows prop with the new page data inside your onPageChange callback. The table will display whatever data you provide in the rows prop.
💡 Loading States with Pagination
Combine pagination with the isLoading prop to show skeleton loaders while fetching new page data. This provides better user feedback during page transitions. See the Loading State documentation for more details.
Overflow Behavior
When pagination is enabled without a specified height, the table will automatically adjust to show all rows on the current page with visible overflow (no internal scrolling). This provides a cleaner user experience for paginated data.
Customizing the Footer
While Simple Table provides a default pagination footer, you can completely customize its appearance and functionality using the footerRenderer prop. This allows you to:
- Match your application's design system
- Add custom pagination controls and navigation
- Display summary statistics or totals
- Include action buttons or additional functionality
💡 Advanced Customization
Learn how to create custom pagination footers with complete control over appearance and behavior. Visit the Footer Renderer documentation for detailed examples and API reference.