DataTable

Composed table experience with toolbar, card view, filters, pagination, and selection.

Quick Preview

Track applicant status, budget, source, and ownership.

Buyer
Customer ID
Email
Status
Budget
Address
Location
Source
Assigned To
Next Action
Date Added
CUS-4823ada.okafor@giddaa.comApproved₦85mNo 5A Beta Glass Road, Off Awolowo Way, Ikoyi, LagosIkoyi, LagosWebsiteMayaSend offerMay 19, 2026
CUS-4824nnamdi.kalu@giddaa.comReview₦64mWisdom Estate, Behind BG Musa Station, Central Business District, AbujaWuse, AbujaReferralTomiSchedule tourMay 20, 2026
CUS-4825zainab.bello@giddaa.comApproved₦120mPlot 14 Aso Drive, Asokoro District, Abuja, Federal Capital TerritoryAsokoro, AbujaAgentIfeVerify docsMay 21, 2026
CUS-4826emeka.eze@giddaa.comReview₦42mNo 23 Admiralty Way, Lekki Phase 1, LagosLekki, LagosInstagramMayaFollow upMay 22, 2026
CUS-4827fatima.musa@giddaa.comApproved₦98mHouse 7, Diplomatic Zone, Maitama, AbujaMaitama, AbujaWebsiteTomiSend invoiceMay 23, 2026

Showing 1 to 5 of 10 entries

Usage

import {
  DataTable,
  DataTableBulkActions,
  DataTableCardGrid,
  DataTableFilter,
  DataTablePageSizeSelect,
  DataTablePagination,
  DataTableRoot,
  DataTableToolbar,
  useDataTable,
  type DataTableColumnDef,
} from "@giddaa-housing/ui/data-table";
import { Button } from "@giddaa-housing/ui/button";
import { TableCard } from "@giddaa-housing/ui/table-card";
import { getPaginationRowModel } from "@tanstack/react-table";
import { DataTableViewSelect } from "@/components/recipes/data-table-view-select";

function ApplicantsTable({ columns, data }) {
  const table = useDataTable({
    columns,
    data,
    enableRowSelection: true,
    getPaginationRowModel: getPaginationRowModel(),
  });

  return (
    <DataTableRoot table={table}>
      <DataTableToolbar>
        <DataTableFilter
          fields={filterFields}
          onApply={(serialized) => {
            setFilter(serialized);
            table.setPageIndex(0);
          }}
        />
        <DataTableViewSelect view={view} onViewChange={setView} />
        <DataTablePageSizeSelect />
      </DataTableToolbar>
      <DataTable />
      <DataTableBulkActions>
        {({ data }) => <Button>Assign {data.length}</Button>}
      </DataTableBulkActions>
      <DataTablePagination />
    </DataTableRoot>
  );
}

Examples

Default Data Table

Use the primitives to create a product-specific wrapper. The wrapper owns filters, search, routing, export buttons, and API state while the UI package keeps the table layout consistent. This example places Filter inside a toolbar popover and applies the selected status outside the table primitive.

Track applicant status, budget, source, and ownership.

Buyer
Customer ID
Email
Status
Budget
Address
Location
Source
Assigned To
Next Action
Date Added
CUS-4823ada.okafor@giddaa.comApproved₦85mNo 5A Beta Glass Road, Off Awolowo Way, Ikoyi, LagosIkoyi, LagosWebsiteMayaSend offerMay 19, 2026
CUS-4824nnamdi.kalu@giddaa.comReview₦64mWisdom Estate, Behind BG Musa Station, Central Business District, AbujaWuse, AbujaReferralTomiSchedule tourMay 20, 2026
CUS-4825zainab.bello@giddaa.comApproved₦120mPlot 14 Aso Drive, Asokoro District, Abuja, Federal Capital TerritoryAsokoro, AbujaAgentIfeVerify docsMay 21, 2026
CUS-4826emeka.eze@giddaa.comReview₦42mNo 23 Admiralty Way, Lekki Phase 1, LagosLekki, LagosInstagramMayaFollow upMay 22, 2026
CUS-4827fatima.musa@giddaa.comApproved₦98mHouse 7, Diplomatic Zone, Maitama, AbujaMaitama, AbujaWebsiteTomiSend invoiceMay 23, 2026

Showing 1 to 5 of 10 entries

This example also references copy-and-own recipes:

Toolbar Filter

Use DataTableFilter for the common toolbar popover. It composes Filter, Popover, FilterContent, FilterResetButton, and FilterApplyButton; your screen still owns what applying filters does.

<DataTableToolbar>
  <DataTableFilter
    fields={filterFields}
    onApply={(serialized, filters) => {
      setFilter(serialized);
      setStructuredFilters(filters);
      table.setPageIndex(0);
    }}
  />
  <DataTablePageSizeSelect />
</DataTableToolbar>

Props

Prop/APITypeDefaultDescription
useDataTableUseDataTableOptions-Convenience hook around useReactTable with Giddaa defaults and optional row-selection column.
DataTableRoot{ table, isLoading?, onRowClick? }-Provider/root that shares the TanStack table instance with child components.
DataTableToolbardiv props-Layout shell for search, filters, page-size select, export buttons, or app actions.
DataTableFilterFilter props plus popover/trigger/button overrides-Convenience toolbar popover for filter rows. Calls onApply; does not mutate table rows, fetch data, or write URL params for you.
DataTable{ table?, isLoading?, emptyState? }-Styled table renderer using the shared TanStack instance.
DataTableCardGrid{ renderCard?, titleColumnId?, detailColumnIds? }-Card-grid renderer using the shared TanStack instance. Use renderCard for product-specific card layouts.
DataTableBulkActionsrender prop or node-Sticky bulk-action bar shown when rows are selected.
DataTablePagination{ table?, page?, pageSize?, pageCount?, totalEntries?, onPageChange?, showControls?, controlAppearance?, showJumpToPage? }-Pagination footer with entry summary, page controls, and jump-to-page. Reads TanStack pagination state by default, or drive it from API state.
DataTablePageSizeSelect{ table?, pageSizeOptions?, pageSize?, onPageSizeChange?, label? }-Page-size select. Reads TanStack state by default, or drive it from API state with pageSize + onPageSizeChange.

Pagination

Pagination and page size can be driven two ways. Both use the same DataTablePagination and DataTablePageSizeSelect components — pick per screen based on where the source of truth lives.

Client-side (TanStack owns pagination)

When the table has all the rows, add getPaginationRowModel and TanStack slices, counts, and navigates for you. The controls bind to the table instance automatically (via prop or DataTableRoot context):

const table = useDataTable({
  columns,
  data, // full dataset
  getPaginationRowModel: getPaginationRowModel(),
});

<DataTablePageSizeSelect /> // reads/writes table.setPageSize
<DataTablePagination />     // reads/writes table.setPageIndex

Server-side (API owns pagination)

Real screens usually fetch one page at a time and get page, pageSize, and a total count back from the API. There are two supported approaches:

A — Controlled TanStack (manualPagination). Keep using the table instance but let your API be the source of truth. TanStack won't slice data; it just tracks state and reports getPageCount() from rowCount:

const table = useDataTable({
  columns,
  data: pageRows, // only the current page from the API
  manualPagination: true,
  rowCount: totalCount, // total across all pages, from the API
  state: { pagination: { pageIndex: page - 1, pageSize } },
  onPaginationChange: (updater) => {
    const next =
      typeof updater === "function"
        ? updater({ pageIndex: page - 1, pageSize })
        : updater;
    setPage(next.pageIndex + 1);
    setPageSize(next.pageSize);
  },
});

<DataTablePageSizeSelect />
<DataTablePagination />

B — Fully controlled (no table wiring). If pagination lives entirely in your API/query state and you don't want to thread it through TanStack, pass the values directly. In this mode neither control touches the table instance:

<DataTablePageSizeSelect
  pageSize={pageSize}
  onPageSizeChange={(size) => {
    setPageSize(size);
    setPage(1); // reset to first page on size change
  }}
/>
<DataTablePagination
  page={page}           // 1-based current page
  pageSize={pageSize}
  totalEntries={totalCount}
  onPageChange={setPage} // called with the next 1-based page
/>

Notes:

  • page is 1-based; TanStack's internal pageIndex is 0-based. The controlled props and onPageChange all speak 1-based pages so they map directly to typical API params.
  • Pass totalEntries + pageSize to render the "Showing 11 to 20 of 132 entries" summary and derive page count. If your API already returns a page count, pass pageCount too.
  • Use showControls, showPreviousNext, showJumpToPage, and showSummary to hide footer sections per screen.
  • Use controlAppearance="icon" for compact previous/next buttons or controlAppearance="control" for labelled Previous/Next buttons.
  • DataTablePagination renders nothing when pageCount <= 1, in either mode.
  • Reset to page 1 when the page size, search, or filters change so you don't request an out-of-range page.

Views

Keep view switching in your application wrapper. The root provides the TanStack table once, then each view can render the same row model differently.

Use the DataTableViewSelect recipe when you want the standard toolbar select for list/card switching.

<DataTableRoot table={table}>
  <DataTableToolbar>{/* view toggle, filters, search */}</DataTableToolbar>
  {view === "list" ? <DataTable /> : null}
  {view === "card" ? (
    <DataTableCardGrid
      renderCard={(row, { action, selection }) => (
        <TableCard
          action={action}
          selection={selection}
          title={row.original.name}
          subtitle={row.original.email}
          details={[
            { label: "Budget", value: row.original.budget },
            { label: "Location", value: row.original.location },
          ]}
        />
      )}
    />
  ) : null}
  {view === "kanban" ? <ApplicantKanban table={table} /> : null}
  <DataTablePagination />
</DataTableRoot>

For simple card views, you can map columns directly:

<DataTableCardGrid
  titleColumnId="name"
  subtitleColumnId="email"
  badgeColumnId="status"
  detailColumnIds={["budget", "location", "assignedTo"]}
/>

For richer screens, prefer renderCard. This avoids forcing your table columns to match the card layout, especially when title and subtitle are composed inside the same table cell. Kanban should stay app-owned until the interaction model stabilizes; it can consume useDataTableContext() or the same table instance.

Accessibility

  • Provide accessible column headers and row action labels.
  • Keep router search params, API fetching, and product filters in the application wrapper.
  • Keep row click behavior separate from interactive controls inside cells.

On this page