Advanced Patterns
Global Configuration
Wrap your application with TableProvider in app/providers.tsx to set global defaults for all table instances. Any instance-level config will override these defaults.
// app/providers.tsx
import { TableProvider } from "@/components/table";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<TableProvider
config={{
pagination: {
defaultPageSize: 25,
pageSizeOptions: [10, 25, 50, 100],
},
features: {
search: true,
filters: true,
columnVisibility: true,
csvExport: true,
cardView: false,
},
i18n: {
locale: "en",
overrides: {
"pagination.of": "out of",
"table.noResults": "Nothing to show here",
},
},
}}
>
{children}
</TableProvider>
);
}Per-Instance Overrides
Pass a config prop directly to ClientSideTable to override any global defaults for that specific instance.
// components/user-table.tsx
import { ClientSideTable } from "@/components/table";
import { columns } from "./columns";
import { data } from "./data";
export function UserTable() {
return (
<ClientSideTable
columns={columns}
data={data}
config={{
features: {
csvExport: false,
cardView: true,
},
pagination: {
defaultPageSize: 10,
},
}}
/>
);
}Dynamic Column Generation
Generate columns programmatically from a schema definition using a buildColumns utility. This is useful when your column structure is determined at runtime or driven by an API response.
// lib/build-columns.ts
import { ColumnDef } from "@tanstack/react-table";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
export interface FieldSchema {
key: string;
label: string;
type: "string" | "date" | "badge";
sortable?: boolean;
hideable?: boolean;
}
export function buildColumns<T>(fields: FieldSchema[]): ColumnDef<T>[] {
return fields.map((field) => ({
accessorKey: field.key,
header: field.label,
cell: ({ getValue }) => {
const value = getValue();
if (field.type === "date") {
return value ? format(new Date(value as string), "MMM d, yyyy") : "—";
}
if (field.type === "badge") {
return value ? <Badge variant="outline">{String(value)}</Badge> : null;
}
return value != null ? String(value) : "—";
},
enableSorting: field.sortable ?? true,
enableHiding: field.hideable ?? true,
}));
}Feature Toggling at Runtime
Use useState and useMemo to toggle table features dynamically based on user interactions or application state.
// components/toggleable-table.tsx
"use client";
import { useState, useMemo } from "react";
import { ClientSideTable } from "@/components/table";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { columns } from "./columns";
import { data } from "./data";
export function ToggleableTable() {
const [filtersEnabled, setFiltersEnabled] = useState(true);
const [exportEnabled, setExportEnabled] = useState(false);
const config = useMemo(
() => ({
features: {
filters: filtersEnabled,
csvExport: exportEnabled,
},
}),
[filtersEnabled, exportEnabled]
);
return (
<div className="space-y-4">
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<Switch
id="filters-toggle"
checked={filtersEnabled}
onCheckedChange={setFiltersEnabled}
/>
<Label htmlFor="filters-toggle">Enable Filters</Label>
</div>
<div className="flex items-center gap-2">
<Switch
id="export-toggle"
checked={exportEnabled}
onCheckedChange={setExportEnabled}
/>
<Label htmlFor="export-toggle">Enable CSV Export</Label>
</div>
</div>
<ClientSideTable columns={columns} data={data} config={config} />
</div>
);
}Multi-Table Setup
Multiple table instances can coexist on the same page. Wrap them in a shared TableProvider for global defaults, then override per-instance as needed.
// app/dashboard/page.tsx
"use client";
import { TableProvider, ClientSideTable } from "@/components/table";
import { userColumns } from "./user-columns";
import { orderColumns } from "./order-columns";
import { users, orders } from "./data";
export default function DashboardPage() {
return (
<TableProvider
config={{
features: {
search: true,
filters: true,
csvExport: false,
},
}}
>
<section className="space-y-2">
<h2 className="text-xl font-semibold">Users</h2>
<ClientSideTable columns={userColumns} data={users} />
</section>
<section className="space-y-2 mt-8">
<h2 className="text-xl font-semibold">Orders</h2>
{/* This instance overrides the global csvExport: false */}
<ClientSideTable
columns={orderColumns}
data={orders}
config={{
features: {
csvExport: true,
},
}}
/>
</section>
</TableProvider>
);
}Config-Driven Table Generation
Define reusable table configurations as named constants using createTableConfig. Import and apply them wherever a consistent preset is needed across your application.
// lib/table-configs.ts
import { createTableConfig } from "@/components/table";
export const ADMIN_TABLE_CONFIG = createTableConfig({
features: {
search: true,
filters: true,
columnVisibility: true,
csvExport: true,
cardView: true,
},
pagination: {
defaultPageSize: 50,
pageSizeOptions: [25, 50, 100],
},
});
export const READONLY_TABLE_CONFIG = createTableConfig({
features: {
search: true,
filters: false,
columnVisibility: false,
csvExport: false,
cardView: false,
},
pagination: {
defaultPageSize: 20,
},
});
export const MINIMAL_TABLE_CONFIG = createTableConfig({
features: {
search: false,
filters: false,
columnVisibility: false,
csvExport: false,
cardView: false,
},
pagination: {
defaultPageSize: 10,
pageSizeOptions: [10],
},
});
// Usage
// <ClientSideTable columns={columns} data={data} config={ADMIN_TABLE_CONFIG} />
// <ClientSideTable columns={columns} data={data} config={READONLY_TABLE_CONFIG} />Plugins
Plugins allow you to extend table behavior with reusable, composable logic. Pass plugins as an array to the table instance to hook into lifecycle events and override rendering.
// lib/table-plugins.ts
import { TablePlugin } from "@/components/table";
// Logs sort, filter, and page change events to the console
export const loggingPlugin: TablePlugin = {
name: "logging",
onSortChange: (sorting) => {
console.log("[Table] Sort changed:", sorting);
},
onFilterChange: (filters) => {
console.log("[Table] Filters changed:", filters);
},
onPageChange: (page) => {
console.log("[Table] Page changed:", page);
},
};
// Replaces the default pagination with a compact single-row layout
export const compactPaginationPlugin: TablePlugin = {
name: "compact-pagination",
renderPagination: ({ page, pageCount, onPageChange }) => (
<div className="flex items-center gap-2 text-sm">
<button
onClick={() => onPageChange(page - 1)}
disabled={page <= 1}
className="px-2 py-1 border rounded disabled:opacity-40"
>
Prev
</button>
<span>
{page} / {pageCount}
</span>
<button
onClick={() => onPageChange(page + 1)}
disabled={page >= pageCount}
className="px-2 py-1 border rounded disabled:opacity-40"
>
Next
</button>
</div>
),
};
// Usage
// <ClientSideTable
// columns={columns}
// data={data}
// plugins={[loggingPlugin, compactPaginationPlugin]}
// />