table-builder

Generate production-ready data tables with sorting, filtering, and pagination.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "table-builder" with this command: npx skills add monkey1sai/openai-cli/monkey1sai-openai-cli-table-builder

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>

  &#x3C;div className="rounded-md border">
    &#x3C;Table>
      &#x3C;TableHeader>
        {table.getHeaderGroups().map((headerGroup) => (
          &#x3C;TableRow key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              &#x3C;TableHead key={header.id}>
                {flexRender(
                  header.column.columnDef.header,
                  header.getContext()
                )}
              &#x3C;/TableHead>
            ))}
          &#x3C;/TableRow>
        ))}
      &#x3C;/TableHeader>
      &#x3C;TableBody>
        {table.getRowModel().rows?.length ? (
          table.getRowModel().rows.map((row) => (
            &#x3C;TableRow key={row.id}>
              {row.getVisibleCells().map((cell) => (
                &#x3C;TableCell key={cell.id}>
                  {flexRender(
                    cell.column.columnDef.cell,
                    cell.getContext()
                  )}
                &#x3C;/TableCell>
              ))}
            &#x3C;/TableRow>
          ))
        ) : (
          &#x3C;TableRow>
            &#x3C;TableCell
              colSpan={columns.length}
              className="h-24 text-center"
            >
              No results.
            &#x3C;/TableCell>
          &#x3C;/TableRow>
        )}
      &#x3C;/TableBody>
    &#x3C;/Table>
  &#x3C;/div>

  &#x3C;DataTablePagination table={table} />
&#x3C;/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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

eslint-prettier-config

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

api-docs-generator

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

rate-limiting-abuse-protection

No summary provided by upstream source.

Repository SourceNeeds Review