A React data table with sorting, search, faceted and range filters, column visibility, pinning and resizing, row selection with bulk actions, pagination, server-side mode and virtualization. Built on React Aria and styled with Tailwind CSS.
Directory
Search, filter, sort by several columns and choose which columns to show.
Name | Status | Role | Team | Start date | Salary | Location |
|---|---|---|---|---|---|---|
Olivia Rhye olivia.rhye@proper.example | Active | Product Designer | Design | Jan 1, 2019 | $60,000 | Lisbon |
Phoenix Diggs phoenix.diggs@proper.example | Active | Backend Developer | Support | Jun 12, 2020 | $149,000 | Toronto |
Lana Mays lana.mays@proper.example | Suspended | Data Analyst | Sales | Nov 23, 2021 | $148,000 | Singapore |
Demi Craig demi.craig@proper.example | Active | Product Manager | Marketing | Apr 6, 2022 | $147,000 | Melbourne |
Candice Hall candice.hall@proper.example | Invited | QA Engineer | Engineering | Sep 17, 2023 | $146,000 | Berlin |
Natali Wilkinson natali.wilkinson@proper.example | Active | Support Lead | Product | Feb 28, 2024 | $145,000 | Nairobi |
Drew Wright drew.wright@proper.example | Active | Frontend Developer | Design | Jul 11, 2025 | $144,000 | Austin |
Orlando Baker orlando.baker@proper.example | Suspended | UX Researcher | Support | Dec 22, 2019 | $143,000 | Lisbon |
Andi Lane andi.lane@proper.example | Active | Product Designer | Sales | May 5, 2020 | $142,000 | Toronto |
Kate Hewitt kate.hewitt@proper.example | Invited | Backend Developer | Marketing | Oct 16, 2021 | $141,000 | Singapore |
1–10 of 48
Installation
npx properui@latest add data-tableUsage
DataTable takes your records and a list of column definitions, and does the rest: sorting,
search, filters, column visibility, selection, pagination, density, and the loading and empty states.
import { DataTable } from "@properui/ui/components/application/data-table/data-table";
import type { DataTableColumn } from "@properui/ui/components/application/data-table/use-data-table";
const columns: DataTableColumn<Person>[] = [
{ id: "name", header: "Name", isRowHeader: true, enableSorting: true },
{ id: "status", header: "Status", enableSorting: true, filter: { type: "select" } },
{ id: "salary", header: "Salary", enableSorting: true, filter: { type: "number-range" } },
{ id: "startDate", header: "Start date", filter: { type: "date-range" } },
];
<DataTable title="People" data={people} columns={columns} selectionMode="multiple" pageSizeOptions={[10, 25, 50]} />;
A column reads row[id] unless you give it an accessor, and renders that value as text unless you
give it a cell. Give exactly one column isRowHeader: it names each row for assistive technology.
Table stays the styling primitive. DataTable renders the same TableCard, Table.Header,
Table.Row and TablePaginationNumbered the table components use, so every
visual detail is shared and Table's own API is unchanged. Reach for Table directly when you want
a static or hand-wired table; reach for DataTable when you want the behaviour.
Sorting
Set enableSorting on a column and click its header to sort; click again to reverse. With
enableMultiSort, earlier sorts are kept as tie-breakers: the column you click last becomes the
primary sort and a small number in each sorted header shows its priority. Pass a sortingFn for a
custom comparator.
Team members
8 peopleName | Status | Role | Start date | Salary |
|---|---|---|---|---|
Candice Hall candice.hall@proper.example | Invited | QA Engineer | Sep 17, 2023 | $146,000 |
Demi Craig demi.craig@proper.example | Active | Product Manager | Apr 6, 2022 | $147,000 |
Drew Wright drew.wright@proper.example | Active | Frontend Developer | Jul 11, 2025 | $144,000 |
Lana Mays lana.mays@proper.example | Suspended | Data Analyst | Nov 23, 2021 | $148,000 |
Natali Wilkinson natali.wilkinson@proper.example | Active | Support Lead | Feb 28, 2024 | $145,000 |
Olivia Rhye olivia.rhye@proper.example | Active | Product Designer | Jan 1, 2019 | $60,000 |
Orlando Baker orlando.baker@proper.example | Suspended | UX Researcher | Dec 22, 2019 | $143,000 |
Phoenix Diggs phoenix.diggs@proper.example | Active | Backend Developer | Jun 12, 2020 | $149,000 |
Filters and column visibility
The toolbar holds the search field, one faceted button per select filter (its options and counts
come from the data unless you pass options), and a Filters panel for text, number-range and
date-range filters; the date range uses DateRangePicker. The Columns menu shows and hides
columns, and showColumnMenu adds a sort, pin and hide menu to every header.
Selection and bulk actions
selectionMode="multiple" adds a checkbox column. Once a row is selected, a bar shows the count,
your bulkActions, and a "Clear selection" button. onSelectionChange reports both the React Aria
selection and the selected records.
Members
24 peopleName | Status | Role | Team | Start date | Salary | |
|---|---|---|---|---|---|---|
Olivia Rhye olivia.rhye@proper.example | Active | Product Designer | Design | Jan 1, 2019 | $60,000 | |
Phoenix Diggs phoenix.diggs@proper.example | Active | Backend Developer | Support | Jun 12, 2020 | $149,000 | |
Lana Mays lana.mays@proper.example | Suspended | Data Analyst | Sales | Nov 23, 2021 | $148,000 | |
Demi Craig demi.craig@proper.example | Active | Product Manager | Marketing | Apr 6, 2022 | $147,000 | |
Candice Hall candice.hall@proper.example | Invited | QA Engineer | Engineering | Sep 17, 2023 | $146,000 | |
Natali Wilkinson natali.wilkinson@proper.example | Active | Support Lead | Product | Feb 28, 2024 | $145,000 | |
Drew Wright drew.wright@proper.example | Active | Frontend Developer | Design | Jul 11, 2025 | $144,000 | |
Orlando Baker orlando.baker@proper.example | Suspended | UX Researcher | Support | Dec 22, 2019 | $143,000 | |
Andi Lane andi.lane@proper.example | Active | Product Designer | Sales | May 5, 2020 | $142,000 | |
Kate Hewitt kate.hewitt@proper.example | Invited | Backend Developer | Marketing | Oct 16, 2021 | $141,000 |
1–10 of 24
Virtualization
virtualized renders the table through React Aria's Virtualizer and TableLayout, so only the
rows in view exist in the DOM. Rows have a fixed height per density, the header stays in view, and
pagination is off by default. Column pinning is not applied in this mode.
All employees
5,000 rowsServer-side mode
Set manual and the table stops sorting, filtering and paging locally. It renders data as the
current page, uses rowCount for the page count, and calls onQueryChange on mount and after every
change with everything a request needs:
const onQueryChange = ({ sorting, globalFilter, columnFilters, pagination }: DataTableQuery) => {
setIsLoading(true);
fetchPeople({ sorting, globalFilter, columnFilters, pagination }).then(({ rows, total }) => {
setResult({ rows, total });
setIsLoading(false);
});
};
<DataTable manual data={result.rows} rowCount={result.total} isLoading={isLoading} onQueryChange={onQueryChange} columns={columns} />;
isLoading swaps the rows for skeleton rows. Searching or filtering resets the query to page 1.
Debounce or cancel stale requests yourself: the demo below drops any response that is not for the
latest query.
Employees
0 resultsSorted, filtered and paginated on the server.
Name | Status | Role | Salary |
|---|---|---|---|
| Loading | |||
| Loading | |||
| Loading | |||
| Loading | |||
| Loading | |||
| Loading | |||
| Loading | |||
| Loading |
0–0 of 0
Pinned columns and sticky header
pinned: "start" or pinned: "end" keeps a column in view while the table scrolls sideways. Give
pinned columns a width, which positions the columns pinned after them. stickyHeader keeps the
header row visible while the body scrolls inside maxHeight.
Compensation
Name | Status | Role | Team | Location | Email | Start date | Salary |
|---|---|---|---|---|---|---|---|
Olivia Rhye | Active | Product Designer | Design | Lisbon | olivia.rhye@proper.example | Jan 1, 2019 | $60,000 |
Phoenix Diggs | Active | Backend Developer | Support | Toronto | phoenix.diggs@proper.example | Jun 12, 2020 | $149,000 |
Lana Mays | Suspended | Data Analyst | Sales | Singapore | lana.mays@proper.example | Nov 23, 2021 | $148,000 |
Demi Craig | Active | Product Manager | Marketing | Melbourne | demi.craig@proper.example | Apr 6, 2022 | $147,000 |
Candice Hall | Invited | QA Engineer | Engineering | Berlin | candice.hall@proper.example | Sep 17, 2023 | $146,000 |
Natali Wilkinson | Active | Support Lead | Product | Nairobi | natali.wilkinson@proper.example | Feb 28, 2024 | $145,000 |
Drew Wright | Active | Frontend Developer | Design | Austin | drew.wright@proper.example | Jul 11, 2025 | $144,000 |
Orlando Baker | Suspended | UX Researcher | Support | Lisbon | orlando.baker@proper.example | Dec 22, 2019 | $143,000 |
Andi Lane | Active | Product Designer | Sales | Toronto | andi.lane@proper.example | May 5, 2020 | $142,000 |
Kate Hewitt | Invited | Backend Developer | Marketing | Singapore | kate.hewitt@proper.example | Oct 16, 2021 | $141,000 |
Ava Cano | Active | Data Analyst | Engineering | Melbourne | ava.cano@proper.example | Mar 27, 2022 | $140,000 |
Koray Franz | Active | Product Manager | Product | Berlin | koray.franz@proper.example | Aug 10, 2023 | $139,000 |
Mollie Wu | Suspended | QA Engineer | Design | Nairobi | mollie.wu@proper.example | Jan 21, 2024 | $138,000 |
Eduard Okumus | Active | Support Lead | Support | Austin | eduard.okumus@proper.example | Jun 4, 2025 | $137,000 |
Zahir Steiner | Invited | Frontend Developer | Sales | Lisbon | zahir.steiner@proper.example | Nov 15, 2019 | $136,000 |
Sienna Morrison | Active | UX Researcher | Marketing | Toronto | sienna.morrison@proper.example | Apr 26, 2020 | $135,000 |
Olivia Baker | Active | Product Designer | Engineering | Singapore | olivia.baker16@proper.example | Sep 9, 2021 | $134,000 |
Phoenix Lane | Suspended | Backend Developer | Product | Melbourne | phoenix.lane17@proper.example | Feb 20, 2022 | $133,000 |
Lana Hewitt | Active | Data Analyst | Design | Berlin | lana.hewitt18@proper.example | Jul 3, 2023 | $132,000 |
Demi Cano | Invited | Product Manager | Support | Nairobi | demi.cano19@proper.example | Dec 14, 2024 | $131,000 |
Resizable columns
resizable wraps the table in React Aria's ResizableTableContainer and adds a ColumnResizer to
every header. Drag the handle, or focus it and use the arrow keys. width, minWidth and
maxWidth on a column set its starting width and its limits.
Resizable columns
Name | Status | Role | Start date | Salary |
|---|---|---|---|---|
Olivia Rhye olivia.rhye@proper.example | Active | Product Designer | Jan 1, 2019 | $60,000 |
Phoenix Diggs phoenix.diggs@proper.example | Active | Backend Developer | Jun 12, 2020 | $149,000 |
Lana Mays lana.mays@proper.example | Suspended | Data Analyst | Nov 23, 2021 | $148,000 |
Demi Craig demi.craig@proper.example | Active | Product Manager | Apr 6, 2022 | $147,000 |
Candice Hall candice.hall@proper.example | Invited | QA Engineer | Sep 17, 2023 | $146,000 |
Natali Wilkinson natali.wilkinson@proper.example | Active | Support Lead | Feb 28, 2024 | $145,000 |
Building your own layout
useDataTable is the hook behind DataTable. It returns the processed rows, the
visibleColumns, sortDescriptor/onSortChange for Table, and setters for every piece of state.
Pass its return value to DataTableToolbar, DataTableViewOptions, DataTableFacetedFilter or
DataTableColumnMenu to reuse the built-in controls in a layout of your own.