Data table card

The table surface: one bordered card holding a title, tabs, a filter toolbar, the grid and a footer — with the same dividers and gutters every time.

Preview

Interactive examples use the same @flux/ui build as production. Toggle theme in the header for light and dark. Motion tokens and layout patterns live under Motion and Patterns in the sidebar.

Live preview

Installation

Add the package to your app (full Next.js + Tailwind steps on Installation):

npm install @payglocal_ui/flux-ui

Usage

Import

import { DataTableCard, TableToolbarActions } from "@payglocal_ui/flux-ui";

Example

// DataTable is the grid. DataTableCard is everything around it — which is
// where tables actually drift: one feature puts its filters above the card,
// another inside it; one draws a divider under the tabs, another does not.
<DataTableCard
  columns={columns}
  data={rows}
  rowKey={(r) => r.id}
  isLoading={isLoading}
  toolbar={<FilterToolbar search={<RotatingSearchInput … />} chips={…} />}
  pagination={{ mode: "page", page, pageSize, total, onPageChange: setPage }}
  onRowClick={(row) => openDetails(row)}
  emptyTitle="No transactions found"
/>

// A named section — for a grid that titles itself, rather than a page-level
// grid whose name is the page header.
<DataTableCard
  title="Top 10 Merchants by Volume"
  description="Last 30 days"
  actions={<ToolbarButton>Refresh</ToolbarButton>}
  columns={columns}
  data={rows}
  rowKey={(r) => r.id}
  maxBodyHeight="34rem"
/>

// The body scrolls inside the card by default, so the toolbar and footer stay
// put and the page does not grow. Pass "none" to let the card grow instead.
<DataTableCard … maxBodyHeight="none" />

// `errorState` replaces the rows when the request failed, `emptyState` when it
// succeeded and returned none. Keeping them apart matters: column headers over
// nothing say "we looked and found none", which is wrong for a failed fetch.
<DataTableCard
  …
  errorState={isError ? <PlaceholderState variant="error" … /> : undefined}
  emptyState={<PlaceholderState variant="no-transactions" … />}
/>

// Below the table's breakpoint, pair it with DataCardList rather than hiding
// columns: both mount, CSS shows one, and they take the same `pagination`
// object so they cannot disagree about which page they are on.
<DataTableCard className="hidden lg:block" … pagination={pagination} />
<DataCardList className="lg:hidden" … pagination={pagination} />