Skip to main content
Version: 1.0.3

Data Table

The DataTable component is a fully-generic DataTable<T> built on React + TypeScript, Framer Motion, Radix UI DropdownMenu, and Tailwind CSS. It composes five standalone hooks — useSort, usePagination, useFilter, useColumnVisibility, useRowSelection — that you can import individually for headless usage. On mobile it automatically switches to a stacked card layout; on desktop it renders a sticky-header table.

Showing 1-5 of 12 results
AJ
Alice Johnson
alice@acme.co
Department
Engineering
Role
Senior Engineer
Status
active
Salary
$142,000
Joined
Mar 15, 2021
BM
Bob Martinez
bob@acme.co
Department
Design
Role
Lead Designer
Status
active
Salary
$128,000
Joined
Jul 22, 2020
CW
Carol White
carol@acme.co
Department
Product
Role
Product Manager
Status
active
Salary
$138,000
Joined
Nov 1, 2019
DC
David Chen
david@acme.co
Department
Engineering
Role
Staff Engineer
Status
pending
Salary
$165,000
Joined
Jan 10, 2023
EM
Eva Müller
eva@acme.co
Department
Marketing
Role
Growth Lead
Status
active
Salary
$115,000
Joined
May 18, 2022
1-5 of 12

Installation

ignix add component data-table

Props

<DataTable>

PropTypeDefaultDescription
dataT[]Required. The dataset to display.
columnsColumn<T>[]Required. Column definitions (see below).
keyExtractor(row: T) => string | numberRequired. Returns a unique key per row; used for selection state and React reconciliation.
bulkActionsBulkAction<T>[][]Actions shown in the floating bulk-action bar when rows are selected.
pageSizeOptionsnumber[][5, 10, 25, 50]Options shown in the "Rows per page" selector.
defaultPageSizenumber5Initial page size.
enableRowSelectionbooleantrueShow per-row checkboxes and the select-all header checkbox.
loadingbooleanfalseShow animated skeleton rows instead of data.
emptyStateMessagestring'No data available.'Message shown when data is empty and loading is false.
noResultsMessagestring'No results found.'Message shown when the global search returns zero results.
theme'light' | 'dark''light'Activates the dark colour scheme (adds the dark class to the root).
onRowClick(row: T) => voidundefinedCallback fired when the user taps a row (used in the mobile card view).
mobileBreakpointnumber768Pixel width below which the mobile card layout is rendered instead of the table.
classNamestringundefinedExtra classes applied to the root wrapper.

Column<T> object

FieldTypeRequiredDescription
keykeyof TThe property key on T this column reads from.
titlestringColumn header label.
sortablebooleanDefaults to true. Set to false to disable sort on this column.
visiblebooleanDefaults to true. Set to false to hide the column on initial render.
render(value: T[keyof T], row: T) => React.ReactNodeCustom cell renderer. Receives the raw cell value and the full row.
classNamestringExtra classes applied to both <th> and <td> for this column.

BulkAction<T> object

FieldTypeRequiredDescription
labelstringButton label.
iconReact.ReactNodeOptional icon rendered to the left of the label.
onClick(selectedRows: T[]) => voidCalled with the full array of selected row objects.
variant'default' | 'destructive''destructive' renders the button in the destructive colour token. Default is 'default'.

Sub-components

All sub-components are exported and can be used independently if you need a fully custom layout.

ComponentDescription
TableHeader<T>Sticky <thead> with sortable column headers and an optional select-all checkbox.
TableRow<T>Animated <tr> with per-row checkbox, striped background, and selection highlight.
ColumnVisibilityDropdown<T>Radix UI dropdown that toggles column visibility with animated entrance.
PaginationFull-featured pagination: page-number buttons (windowed), rows-per-page selector, and item range label.
BulkActionBar<T>Fixed floating action bar (bottom-centre) that slides in when rows are selected; supports arbitrary actions.
SkeletonRowPulsing skeleton row used during the loading state.
MobileCardView<T>Stacked card layout rendered on narrow viewports; first column becomes the card title.
MobilePaginationMobile-optimised pagination with a page-size menu, simplified page buttons, and a "jump to page" input.

Hooks

Each hook manages a single concern and can be imported independently for headless use.

useSort<T>(data, initialSortKey?)

const { sortedData, sortKey, sortDirection, toggleSort } = useSort(data);
// toggleSort(key) toggles between 'asc' ↔ 'desc' for the active column.
// When switching columns, sorting starts at 'asc'.
ReturnTypeDescription
sortedDataT[]Data sorted by the active key and direction.
sortKeykeyof T | nullCurrently active sort column.
sortDirection'asc' | 'desc'Current sort direction.
toggleSort(key: keyof T) => voidToggle sort for the given column.

usePagination<T>(data, pageSize)

const { currentPage, totalPages, currentData, nextPage, previousPage, goToPage } = usePagination(data, 10);
ReturnTypeDescription
currentPagenumberActive page (1-indexed).
totalPagesnumberTotal number of pages.
currentDataT[]Slice of data for the current page.
startIndexnumber0-based index of the first item on the current page.
endIndexnumber0-based index of the last item on the current page.
nextPage() => voidAdvance to the next page.
previousPage() => voidGo back one page.
goToPage(page: number) => voidJump to an arbitrary page.

useFilter<T>(data, columns)

const { filteredData, searchTerm, setSearchTerm } = useFilter(data, columns);
// Searches across all provided columns (i.e., the columns passed into useFilter).
ReturnTypeDescription
filteredDataT[]Data filtered by searchTerm across all columns.
searchTermstringCurrent search query.
setSearchTerm(term: string) => voidUpdate the search query.

useColumnVisibility<T>(initialColumns)

const { visibleColumns, toggleColumn, getVisibleColumns } = useColumnVisibility(columns);
ReturnTypeDescription
visibleColumnsSet<keyof T>Set of currently visible column keys.
toggleColumn(key: keyof T) => voidShow or hide the given column.
getVisibleColumnsColumn<T>[]Filtered array of Column<T> objects that are currently visible.

useRowSelection<T>(data, keyExtractor)

const { selectedRows, toggleRow, toggleAll, clearSelection, getSelectedRowData, isAllSelected, isIndeterminate } =
useRowSelection(currentPageData, (row) => row.id);
ReturnTypeDescription
selectedRowsSet<string | number>Set of selected row keys.
toggleRow(row: T) => voidToggle selection for a single row.
toggleAll() => voidSelect all rows on the current page, or deselect all if all are selected.
clearSelection() => voidDeselect all rows.
getSelectedRowData() => T[]Returns the full row objects for all selected keys.
isAllSelectedbooleanTrue when every row on the current page is selected.
isIndeterminatebooleanTrue when some but not all rows are selected (drives the indeterminate checkbox state).

Examples

Basic table

import { DataTable, Column } from '@ignix-ui/data-table';

interface User {
id: string;
name: string;
email: string;
role: string;
}

// Example data
const users: User[] = [
{ id: '1', name: 'Alice', email: 'alice@example.com', role: 'Admin' },
{ id: '2', name: 'Bob', email: 'bob@example.com', role: 'User' },
];

const columns: Column<User>[] = [
{ key: 'name', title: 'Name', sortable: true },
{ key: 'email', title: 'Email', sortable: true },
{ key: 'role', title: 'Role', sortable: true },
];

<DataTable>
data={users}
columns={columns}
keyExtractor={(row) => row.id}
/>

Custom cell renderer with status badge

// All badge colours use CSS custom-property tokens — no hardcoded colours.
const STATUS_STYLES: Record<Status, string> = {
active: 'bg-primary/10 text-primary border border-primary/20',
inactive: 'bg-muted text-muted-foreground border border-border',
pending: 'bg-secondary text-secondary-foreground border border-border',
};

const columns: Column<Employee>[] = [
{
key: 'name',
title: 'Name',
sortable: true,
render: (_, row) => (
<div className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-full bg-primary flex items-center justify-center text-primary-foreground text-xs font-bold">
{row.name[0]}
</div>
<span className="font-medium text-foreground">{row.name}</span>
</div>
),
},
{
key: 'status',
title: 'Status',
sortable: true,
render: (value) => (
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium capitalize ${STATUS_STYLES[value as Status]}`}>
{value as string}
</span>
),
},
];

With bulk actions

import { DataTable, BulkAction } from '@ignix-ui/data-table';

interface User {
id: string;
name: string;
email: string;
role: string;
}

// Example data
const users: User[] = [
{ id: '1', name: 'Alice', email: 'alice@example.com', role: 'Admin' },
{ id: '2', name: 'Bob', email: 'bob@example.com', role: 'User' },
];

const bulkActions: BulkAction<User>[] = [
{
label: 'Delete',
variant: 'destructive',
onClick: (rows) => handleDelete(rows),
},
{
label: 'Export CSV',
variant: 'default',
onClick: (rows) => exportToCsv(rows),
},
];

<DataTable<User>
data={users}
columns={columns}
keyExtractor={(row) => row.id}
bulkActions={bulkActions}
enableRowSelection={true}
/>

Hidden column by default

const columns: Column<Employee>[] = [
{ key: 'name', title: 'Name', sortable: true },
{ key: 'email', title: 'Email', sortable: true },
{ key: 'salary', title: 'Salary', sortable: true, visible: false }, // hidden initially
];

Headless — using hooks independently

import { useSort, useFilter, usePagination } from '@ignix-ui/data-table';

function MyCustomTable({ data }: { data: Product[] }) {
const { filteredData, searchTerm, setSearchTerm } = useFilter(data, columns);
const { sortedData, sortKey, sortDirection, toggleSort } = useSort(filteredData);
const { currentData, currentPage, totalPages, nextPage, previousPage } = usePagination(sortedData, 10);

return (
// ... your fully custom markup using the hook return values
);
}

Row click handler (mobile + desktop)

<DataTable<Product>
data={products}
columns={columns}
keyExtractor={(row) => row.id}
onRowClick={(row) => router.push(`/products/${row.id}`)}
/>