Documentation
Pagination
Split large datasets into pages with enablePagination.
Enable pagination
Set enablePagination and optionally rowsPerPage (default 10). The table slices rows client-side and shows the default footer.
React TSX
Copy
<SimpleTable columns={columns} rows={rows} rowsPerPage={20} enablePagination={true} />
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows":rows-per-page="20":enable-pagination="true"/>
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"[rowsPerPage]="20"[enablePagination]="true"></simple-table>
Svelte
Copy
<SimpleTable {columns} {rows} rowsPerPage={20} enablePagination={true} />
Solid TSX
Copy
<SimpleTable columns={columns} rows={rows} rowsPerPage={20} enablePagination={true} />
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,rowsPerPage: 20,enablePagination: true,});
Server-side pagination
Set serverSidePagination so the table does not slice rows. Pass totalRowCount and load each page in onPageChange. Pair with isLoading while fetching.
React TSX
Copy
const [rows, setRows] = useState([]);const [isLoading, setIsLoading] = useState(false);const handlePageChange = async (page) => {setIsLoading(true);setRows(await fetchPage(page));setIsLoading(false);};<SimpleTablecolumns={columns}rows={rows}enablePaginationserverSidePaginationrowsPerPage={25}totalRowCount={1000}isLoading={isLoading}onPageChange={handlePageChange}/>
Vue SFC
Copy
<script setup>import { ref } from "vue";const rows = ref([]);const isLoading = ref(false);const handlePageChange = async (page) => {isLoading.value = true;rows.value = await fetchPage(page);isLoading.value = false;};</script><template><SimpleTable:columns="columns":rows="rows":enable-pagination="true":server-side-pagination="true":rows-per-page="25":total-row-count="1000":is-loading="isLoading":on-page-change="handlePageChange"/></template>
Angular
Copy
rows = [];isLoading = false;handlePageChange = async (page: number) => {this.isLoading = true;this.rows = await fetchPage(page);this.isLoading = false;};<simple-table[columns]="columns"[rows]="rows"[enablePagination]="true"[serverSidePagination]="true"[rowsPerPage]="25"[totalRowCount]="1000"[isLoading]="isLoading"[onPageChange]="handlePageChange"></simple-table>
Svelte
Copy
<script>let rows = $state([]);let isLoading = $state(false);const handlePageChange = async (page) => {isLoading = true;rows = await fetchPage(page);isLoading = false;};</script><SimpleTable{columns}{rows}enablePagination={true}serverSidePagination={true}rowsPerPage={25}totalRowCount={1000}isLoading={isLoading}onPageChange={handlePageChange}/>
Solid TSX
Copy
const [rows, setRows] = createSignal([]);const [isLoading, setIsLoading] = createSignal(false);const handlePageChange = async (page) => {setIsLoading(true);setRows(await fetchPage(page));setIsLoading(false);};<SimpleTablecolumns={columns}rows={rows()}enablePaginationserverSidePaginationrowsPerPage={25}totalRowCount={1000}isLoading={isLoading()}onPageChange={handlePageChange}/>
TypeScript
Copy
let rows = [];const table = new SimpleTableVanilla(container, {columns,rows,enablePagination: true,serverSidePagination: true,rowsPerPage: 25,totalRowCount: 1000,onPageChange: async (page) => {table.update({ isLoading: true });rows = await fetchPage(page);table.update({ rows, isLoading: false });},});
Custom footer
Replace the default pagination UI with Footer Renderer.
Example
Client-side pagination with the default footer. Use Code or StackBlitz for the full example.
React TSX
Copy
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 columns={paginationConfig.headers}37 height={height ?? "auto"}38 isLoading={isLoading}39 onNextPage={onNextPage}40 rows={rows}41 rowsPerPage={PAGINATION_ROWS_PER_PAGE}42 enablePagination43 theme={theme}44 />45 );46};4748export default PaginationDemo;
Vue SFC
Copy
1<template>2 <SimpleTable3 :columns="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 :enable-pagination="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>
Angularpagination-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, 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 [columns]="headers"14 [height]="height"15 [theme]="theme"16 [enablePagination]="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: AngularColumnDef[] = 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 { AngularColumnDef, Row } from "@simple-table/angular";555657export const PAGINATION_ROWS_PER_PAGE = 9;5859export const paginationHeaders: AngularColumnDef[] = [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 enablePagination: true,104 },105} as const;106
Svelte
Copy
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 columns={paginationConfig.headers}32 height={height ?? "auto"}33 {isLoading}34 {onNextPage}35 {rows}36 rowsPerPage={PAGINATION_ROWS_PER_PAGE}37 enablePagination={true}38 {theme}39/>
Solid TSX
Copy
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 columns={paginationConfig.headers}34 height={props.height ?? "auto"}35 isLoading={isLoading()}36 onNextPage={onNextPage}37 rows={rows()}38 rowsPerPage={PAGINATION_ROWS_PER_PAGE}39 enablePagination={true}40 theme={props.theme}41 />42 );43}
TypeScriptPaginationDemo.ts
Copy
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 columns: paginationConfig.headers,14 rows,15 height: options?.height ?? "auto",16 theme: options?.theme,17 enablePagination: 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 { ColumnDef, Row } from "simple-table-core";454647export const PAGINATION_ROWS_PER_PAGE = 9;4849export const paginationHeaders: ColumnDef[] = [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 enablePagination: true,94 },95} as const;96
Props
Pagination Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
enablePaginationboolean | Optional | Enables pagination and shows the default footer controls. | |
rowsPerPagenumber | Optional | Rows per page. Defaults to 10. | |
serverSidePaginationboolean | Optional | When true, disables internal slicing — supply the current page of rows yourself. | |
totalRowCountnumber | Optional | Total rows on the server (used with serverSidePagination to compute pages). | |
onPageChange(page: number) => void | Promise<void> | Optional | Fires when the page changes. Use to fetch the next page for server-side mode. | |
isLoadingboolean | Optional | Show loading skeletons while page data is fetching. |