Agent skill
table-builder
Creates data table patterns with filtering, sorting, pagination, row actions, column configuration, server/client rendering modes, and empty/loading states. Use when building "data tables", "list views", "admin tables", or "data grids".
Install this agent skill to your Project
npx add-skill https://github.com/patricio0312rev/skills/tree/main/frontend/table-builder
SKILL.md
Table Builder
Generate production-ready data tables with sorting, filtering, and pagination.
Core Workflow
- Define columns: Column configuration with types
- Choose mode: Server-side or client-side rendering
- Add features: Sorting, filtering, pagination, search
- Row actions: Edit, delete, view actions
- Empty states: No data and error views
- Loading states: Skeletons and suspense
- Mobile responsive: Stack columns or horizontal scroll
Column Configuration
import { ColumnDef } from "@tanstack/react-table";
export const columns: ColumnDef<User>[] = [
{
accessorKey: "id",
header: "ID",
cell: ({ row }) => (
<span className="font-mono text-sm">{row.original.id}</span>
),
},
{
accessorKey: "name",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
),
cell: ({ row }) => (
<div className="font-medium">{row.getValue("name")}</div>
),
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const status = row.getValue("status") as string;
return (
<Badge variant={status === "active" ? "success" : "secondary"}>
{status}
</Badge>
);
},
},
{
id: "actions",
cell: ({ row }) => <RowActions row={row} />,
},
];
React Table Implementation
"use client";
import {
useReactTable,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
getFilteredRowModel,
flexRender,
} from "@tanstack/react-table";
export function DataTable<TData, TValue>({
columns,
data,
}: {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onPaginationChange: setPagination,
state: { sorting, columnFilters, pagination },
});
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<Input
placeholder="Search..."
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
onChange={(e) =>
table.getColumn("name")?.setFilterValue(e.target.value)
}
className="max-w-sm"
/>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<DataTablePagination table={table} />
</div>
);
}
Server-Side Pattern
// app/users/page.tsx
import { DataTable } from "@/components/ui/data-table";
async function getUsers(params: {
page: number;
pageSize: number;
sortBy?: string;
sortOrder?: "asc" | "desc";
search?: string;
}) {
const response = await fetch(`/api/users?${new URLSearchParams(params)}`);
return response.json();
}
export default async function UsersPage({
searchParams,
}: {
searchParams: { page?: string; search?: string };
}) {
const page = Number(searchParams.page) || 1;
const search = searchParams.search || "";
const { data, totalPages } = await getUsers({ page, pageSize: 10, search });
return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<DataTable columns={columns} data={data} totalPages={totalPages} />
</div>
);
}
Pagination Component
export function DataTablePagination({ table }: { table: Table<any> }) {
return (
<div className="flex items-center justify-between px-2">
<div className="text-sm text-gray-700">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected
</div>
<div className="flex items-center space-x-6">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium">Rows per page</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => table.setPageSize(Number(value))}
>
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
</Select>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
}
Row Actions
function RowActions({ row }: { row: Row<User> }) {
const user = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(user.id)}
>
Copy ID
</DropdownMenuItem>
<DropdownMenuItem onClick={() => router.push(`/users/${user.id}`)}>
View Details
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleEdit(user)}>
Edit
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600"
onClick={() => handleDelete(user)}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
Empty State
export function EmptyState() {
return (
<div className="flex flex-col items-center justify-center py-12">
<div className="rounded-full bg-gray-100 p-6">
<InboxIcon className="h-12 w-12 text-gray-400" />
</div>
<h3 className="mt-4 text-lg font-semibold">No data found</h3>
<p className="mt-2 text-sm text-gray-600">
Get started by creating a new record.
</p>
<Button className="mt-4">Create New</Button>
</div>
);
}
Loading Skeleton
export function TableSkeleton() {
return (
<div className="space-y-4">
<Skeleton className="h-10 w-64" />
<div className="rounded-md border">
{Array.from({ length: 10 }).map((_, i) => (
<div key={i} className="flex gap-4 border-b p-4">
<Skeleton className="h-6 w-full" />
</div>
))}
</div>
</div>
);
}
Best Practices
- Use TanStack Table for complex features
- Server-side pagination for large datasets
- Debounce search inputs
- Persist sorting/filtering in URL params
- Mobile: Horizontal scroll or card view
- Accessibility: Keyboard navigation, ARIA
Output Checklist
- Column definitions with types
- Sorting functionality
- Filtering/search
- Pagination controls
- Row actions menu
- Empty state component
- Loading skeleton
- Mobile responsive
- URL state persistence
- Accessibility attributes
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
rate-limiting-abuse-protection
Implements rate limiting and abuse prevention with per-route policies, IP/user-based limits, sliding windows, safe error responses, and observability. Use when adding "rate limiting", "API protection", "abuse prevention", or "DDoS protection".
rbac-permissions-builder
Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization".
websocket-realtime-builder
Implements real-time features using WebSockets with Socket.io, rooms, authentication, and reconnection handling. Use when users request "real-time updates", "WebSocket", "Socket.io", "live chat", or "push notifications".
webhook-receiver-hardener
Secures webhook receivers with signature verification, retry handling, deduplication, idempotency keys, and error responses. Provides verification code, dedupe storage strategy, runbook for incidents. Use when implementing "webhooks", "webhook security", "event receivers", or "third-party integrations".
auth-module-builder
Implements secure authentication patterns including login/registration, session management, JWT tokens, password hashing, cookie settings, and CSRF protection. Provides auth routes, middleware, security configurations, and threat model documentation. Use when building "authentication", "login system", "JWT auth", or "session management".
rest-to-graphql-migrator
Migrates REST APIs to GraphQL incrementally with schema stitching, REST datasources, and gradual endpoint migration. Use when users request "migrate to GraphQL", "REST to GraphQL", "GraphQL wrapper", or "API modernization".
Didn't find tool you were looking for?