patricio0312rev/skills268 installs
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".
How do I install this agent skill?
npx skills add https://github.com/patricio0312rev/skills --skill table-builderIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides standard UI templates and components for building data tables in React using the TanStack Table library. No security issues were detected.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
- Runlayerwarn
1/1 file flagged
- ZeroLeakspass
Score: 93/100 · 2 sections analyzed
What does this agent skill do?
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
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/patricio0312rev/skills/table-builder">View table-builder on skillZs</a>