Help us improve
Share bugs, ideas, or general feedback.
From fullstack-agents
Create Next.js data table pages with SSR initial load, simplified state management, and server-response-based UI updates. Use when asked to create a new data table page, entity management page, CRUD table, or admin list view. Generates page.tsx (SSR), table components, columns, context, actions, and API routes following a proven architecture with centralized reusable data-table component.
npx claudepluginhub adelabdelgawad/fullstack-agents --plugin fullstack-agentsHow this skill is triggered — by the user, by Claude, or both
Slash command
/fullstack-agents:data-tableThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create production-ready data table pages with:
examples.mdreferences/api-routes-pattern.mdreferences/columns-pattern.mdreferences/context-pattern.mdreferences/data-table-requirements.mdreferences/edit-sheet-pattern.mdreferences/form-sheet-guard.mdreferences/select-in-tables.mdreferences/table-component-pattern.mdreferences/types-pattern.mdscripts/helper.pyBuilds headless data tables with TanStack Table v8 featuring server-side pagination, filtering, sorting, virtualization for Cloudflare Workers + D1 databases and TanStack Query integration.
Guides building/reviewing SaaS data tables with alignment rules, pagination vs infinite scroll, DataTable/IndexTable patterns, column defaults, bulk actions, sorting, filtering, row selection, and responsive table-to-card.
Guides Next.js data fetching with SSG, SSR, ISR, fetch caching strategies like force-cache and no-store, revalidation, dynamic rendering, and generateStaticParams for data-driven apps.
Share bugs, ideas, or general feedback.
Create production-ready data table pages with:
useState) as the default — SWR only when justified@/components/data-tableThe project includes a comprehensive reusable data-table component library at @/components/data-table. Always use these components — never build table UI from scratch.
components/data-table/
├── index.ts # Main exports (14 components)
├── types.ts # Shared type definitions
├── table/
│ ├── data-table.tsx # Core TanStack Table wrapper (generic <TData>)
│ ├── data-table-bar.tsx # Flexible 3-section toolbar (left/middle/right)
│ └── pagination.tsx # Server-side pagination synced to URL params
├── controls/
│ ├── search-input.tsx # Debounced URL-synced search (default 2000ms)
│ ├── column-toggle.tsx # Column visibility dropdown
│ ├── sort-list.tsx # Advanced multi-column sort with drag-reorder
│ ├── column-header.tsx # Per-column sort header dropdown
│ └── refresh-button.tsx # Manual refresh trigger
├── filters/
│ ├── status-filter-bar.tsx # Tab-style status filters (All/Active/Inactive)
│ └── status-badge-filter.tsx # Badge-style status filters (inline mode available)
├── actions/
│ ├── selection-display.tsx # Selected count with clear button
│ ├── export-button.tsx # CSV export of visible columns
│ ├── print-button.tsx # HTML/PDF print of table
│ ├── enable-button.tsx # Bulk enable with confirmation dialog
│ └── disable-button.tsx # Bulk disable with confirmation dialog
└── ui/
├── button.tsx # Custom button with icon + tooltip
├── status-badge.tsx # Status indicator (Online/Offline/Warning)
└── status-circle.tsx # Circular status with click-filtering
import { DataTable } from "@/components/data-table";
<DataTable
data={items}
columns={columns}
onRowSelectionChange={(selectedRows) => setSelected(selectedRows)}
renderToolbar={(table) => <MyToolbar table={table} />}
isLoading={isLoading}
enableRowSelection={true}
enableSorting={true}
/>
Key features: Generic <TData>, column visibility, row selection, column resizing, RTL support, i18n, loading overlay.
import { DynamicTableBar } from "@/components/data-table";
// Header bar (search, filters, column toggle)
<DynamicTableBar
variant="header"
left={<SearchInput />}
middle={<StatusBadgeFilter totalCount={25} activeCount={20} inactiveCount={5} />}
right={<ColumnToggleButton table={table} />}
/>
// Controller bar (selection count, bulk actions) — shows accent bg when items selected
<DynamicTableBar
variant="controller"
hasSelection={selectedCount > 0}
left={<SelectionDisplay selectedCount={3} onClearSelection={clear} i18n={{...}} />}
right={
<>
<EnableButton selectedIds={ids} onEnable={enable} />
<DisableButton selectedIds={ids} onDisable={disable} />
</>
}
/>
import { Pagination } from "@/components/data-table";
<Pagination
currentPage={page}
totalPages={totalPages}
pageSize={limit}
totalItems={totalItems}
/>
Syncs page and limit URL params automatically. Supports page sizes 10/25/50/100, first/prev/next/last buttons, entry count display.
import { StatusBadgeFilter, StatusFilterBar } from "@/components/data-table";
// Badge-style (preferred, supports inline mode for embedding in toolbars)
<StatusBadgeFilter
totalCount={total}
activeCount={activeCount}
inactiveCount={inactiveCount}
inline={true} // Render without wrapper for embedding in DynamicTableBar
/>
// Tab-style alternative
<StatusFilterBar totalCount={total} activeCount={activeCount} inactiveCount={inactiveCount} />
Both filter on is_active URL param and reset pagination to page 1 on change.
import { SearchInput, DataTableSortList, DataTableColumnHeader, ColumnToggleButton } from "@/components/data-table";
// Debounced search synced to URL ?filter= param
<SearchInput placeholder="Search..." debounceMs={2000} urlParam="filter" />
// Multi-column sort with drag reorder
<DataTableSortList sortableColumns={[
{ id: "firstName", label: "First Name" },
{ id: "role", label: "Role" },
]} />
// Per-column sort header (use in column definitions)
<DataTableColumnHeader column={column} title="Name" />
// Column visibility toggle
<ColumnToggleButton table={table} />
Sort syncs to URL: ?sort=firstName:asc,role:desc
import { SelectionDisplay, EnableButton, DisableButton, ExportButton, PrintButton } from "@/components/data-table";
<SelectionDisplay selectedCount={3} onClearSelection={clear} i18n={{ selected: "{count} {item} selected", clearSelection: "Clear", itemName: "user" }} />
<EnableButton selectedIds={ids} onEnable={handleEnable} />
<DisableButton selectedIds={ids} onDisable={handleDisable} />
<ExportButton table={table} />
<PrintButton table={table} title="Users" />
<TData>: All components use TypeScript genericsltr: / rtl: Tailwind classes throughoutDynamicTableBar + controls = flexible toolbarsuseConfirmationDialog hookThis application uses Strategy A (Simple Fetching) exclusively. All current tables use useState + server response updates.
Does this table's data change without user action?
| Answer | Strategy | Use When |
|---|---|---|
| No | A: Simple Fetching (Default) | Settings, admin CRUD, most entity tables |
| Yes | B: SWR Fetching | Dashboards, multi-user editing, live monitoring |
useState for local data managementuseSWR with documented justificationDecision framework: nextjs/references/data-fetching-strategy.md
app/(pages)/[section]/[entity]/
├── page.tsx # SSR entry point
├── context/
│ └── [entity]-actions-context.tsx # Actions provider
└── _components/
├── table/
│ ├── [entity]-table.tsx # Main client wrapper (useState)
│ ├── [entity]-table-body.tsx # DataTable + columns
│ ├── [entity]-table-columns.tsx# Column definitions
│ ├── [entity]-table-controller.tsx # Toolbar/bulk actions
│ └── [entity]-table-actions.tsx # Bulk action hooks
├── actions/
│ ├── add-[entity]-button.tsx # Add button + sheet
│ └── actions-menu.tsx # Row action menu
├── modal/
│ ├── add-[entity]-sheet.tsx # Create form
│ ├── edit-[entity]-sheet.tsx # Edit form
│ └── view-[entity]-sheet.tsx # View details
└── sidebar/
└── status-panel.tsx # Stats sidebar
@/components/data-table — never build table UI from scratchDefine response types in @/lib/types/api/[entity].ts. See references/types-pattern.md.
Create server actions in @/lib/actions/[entity].actions.ts.
page.tsx)import { auth } from "@/lib/auth/server-auth";
import { get[Entity]s } from "@/lib/actions/[entity].actions";
import { redirect } from "next/navigation";
import [Entity]Table from "./_components/table/[entity]-table";
export default async function [Entity]Page({
searchParams,
}: {
searchParams: Promise<{
is_active?: string;
search?: string;
page?: string;
limit?: string;
}>;
}) {
const session = await auth();
if (!session?.accessToken) redirect("/login");
const params = await searchParams;
const pageNumber = Number(params.page) || 1;
const limitNumber = Number(params.limit) || 10;
const skip = (pageNumber - 1) * limitNumber;
const data = await get[Entity]s(limitNumber, skip, {
is_active: params.is_active,
search: params.search,
});
return <[Entity]Table initialData={data} />;
}
See references/table-component-pattern.md for the full pattern with:
useState with initialData (Strategy A — default)updateItems() function for state mutationActionsProvider wrapperDataTable, DynamicTableBar, Pagination, and filter componentsSee references/columns-pattern.md for:
updatingIds loading statesDataTableColumnHeader for sortable column headersSee references/context-pattern.md for:
See references/edit-sheet-pattern.md for:
// ✅ CORRECT: Use server response
const updated = await api.put<EntityResponse>(`/entity/${id}`, body);
updateItems([updated]); // Update local state with server data
// ❌ WRONG: Optimistic update
const optimistic = { ...current, ...changes };
setData({ items: [...items.filter(i => i.id !== id), optimistic] });
// React state — no SWR
const [data, setData] = useState<Response>(initialData);
const updateItems = (serverResponse: Item[]) => {
setData(current => {
if (!current) return current;
const responseMap = new Map(serverResponse.map(i => [i.id, i]));
return {
...current,
items: current.items.map(item =>
responseMap.has(item.id) ? responseMap.get(item.id)! : item
),
};
});
};
// In [entity]-table-body.tsx — compose using data-table components
import { DataTable, DynamicTableBar, SearchInput, StatusBadgeFilter, ColumnToggleButton, RefreshButton } from "@/components/data-table";
<DataTable
data={items}
columns={columns}
onRowSelectionChange={handleSelectionChange}
renderToolbar={(table) => (
<>
<DynamicTableBar
variant="header"
left={<SearchInput />}
middle={<StatusBadgeFilter inline totalCount={total} activeCount={active} inactiveCount={inactive} />}
right={
<>
<DataTableSortList sortableColumns={sortableColumns} />
<ColumnToggleButton table={table} />
<RefreshButton onRefresh={onRefresh} />
</>
}
/>
{selectedCount > 0 && (
<DynamicTableBar
variant="controller"
hasSelection
left={<SelectionDisplay selectedCount={selectedCount} onClearSelection={clear} i18n={i18n} />}
right={
<>
<EnableButton selectedIds={selectedIds} onEnable={handleEnable} />
<DisableButton selectedIds={selectedIds} onDisable={handleDisable} />
</>
}
/>
)}
</>
)}
/>
src/frontend/app/(pages)/setting/users/ — canonical users table (Strategy A)src/frontend/app/(pages)/setting/roles/ — roles table (Strategy A)Ensure project has:
@tanstack/react-table - Table primitives@/components/data-table - Reusable table components (must exist)swr - Only if using Strategy B (SWR fetching)lib/actions/useState with initialDataDataTable, DynamicTableBar, filters, controls from @/components/data-tableDataTableColumnHeader for sortable headersEnableButton/DisableButton with confirmation