agentsclimarketplace

Data tables

Skill param087/saas-ui-skills/skills/data-tables

Production-grade SaaS UI skills for AI coding agents (React + Tailwind + shadcn/ui) — Codex, Claude Code, Cursor, OpenCode. One npx command to install.

Install
npx -y skills add param087/saas-ui-skills --skill data-tables

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when building data tables — sorting, filtering, pagination, row selection and bulk actions with TanStack Table + shadcn/ui, plus the loading, empty, and error states tables almost always forget.

SKILL.md

4.1 KB, as published. Nobody here has run it

Data Tables

Overview

Tables are where SaaS apps live (users, invoices, logs). Build them with TanStack Table (headless logic: sorting, filtering, pagination, selection) rendered through shadcn/ui Table primitives. Separate column definitions from the table shell so every table in the app behaves consistently.

When to use

  • Displaying lists of records with sort/filter/paginate.
  • Bulk actions on selected rows.
  • A <table> is being hand-built with manual sort state.

Column definitions

import { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";

export type Invoice = { id: string; customer: string; status: "paid" | "open" | "void"; amount: number };

export const columns: ColumnDef<Invoice>[] = [
  {
    id: "select",
    header: ({ table }) => (
      <Checkbox
        checked={table.getIsAllPageRowsSelected()}
        onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
        aria-label="Select all"
      />
    ),
    cell: ({ row }) => (
      <Checkbox
        checked={row.getIsSelected()}
        onCheckedChange={(v) => row.toggleSelected(!!v)}
        aria-label="Select row"
      />
    ),
    enableSorting: false,
  },
  { accessorKey: "customer", header: "Customer" },
  {
    accessorKey: "status",
    header: "Status",
    cell: ({ row }) => <Badge variant={row.original.status === "paid" ? "success" : "secondary"}>{row.original.status}</Badge>,
  },
  {
    accessorKey: "amount",
    header: () => <div className="text-right">Amount</div>,
    cell: ({ row }) => <div className="text-right tabular-nums">${(row.original.amount / 100).toFixed(2)}</div>,
  },
];

The reusable shell

const table = useReactTable({
  data, columns,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  state: { sorting, rowSelection, globalFilter },
  onSortingChange: setSorting,
  onRowSelectionChange: setRowSelection,
});

Render header/body from table.getHeaderGroups() / table.getRowModel().rows. Always handle the three non-happy states:

{isLoading ? (
  <TableRow><TableCell colSpan={columns.length}><Skeleton className="h-8 w-full" /></TableCell></TableRow>
) : rows.length === 0 ? (
  <TableRow><TableCell colSpan={columns.length} className="h-24 text-center text-muted-foreground">No results.</TableCell></TableRow>
) : (
  rows.map((row) => <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>…</TableRow>
)}

Bulk actions

When table.getSelectedRowModel().rows.length > 0, show an action bar:

{selectedCount > 0 && (
  <div className="flex items-center gap-2 rounded-md border bg-muted/50 px-3 py-2 text-sm">
    <span>{selectedCount} selected</span>
    <Button size="sm" variant="destructive" onClick={deleteSelected}>Delete</Button>
  </div>
)}

Server-side at scale

For large datasets, set manualPagination/manualSorting/manualFiltering: true and pass state to your API. Keep filter/sort/page in the URL (searchParams) so views are shareable and survive refresh.

Pitfalls

  • No loading/empty/error states — the #1 omission; a blank table looks broken.
  • Right-align numbers + tabular-nums — left-aligned currency is hard to scan.
  • Client-paginating 50k rows — move to server-side; don't ship it all.
  • Selection lost on page change unless you key it by row id.
  • Non-accessible sort headers — make them <button>s with aria-sort.
  • Filter/sort only in component state — put it in the URL for shareable views.

Hand-off

A consistent table that pairs with empty-and-loading-states (skeletons), modals-and-dialogs (row edit/confirm), and notifications-and-toasts (bulk-action feedback).

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.