Data Table
A package-backed Data Table composition for sortable, filterable, selectable, paginated, and action-oriented table screens.
import { DataTable, DataTableContent, DataTableToolbar } from 'radcn/data-table'Live package example
Port the upstream payments table with native filter forms, hideable columns, row actions, selection, pagination, and package-owned Data Table slots.
Data Table Demo
Port the upstream payments table with native filter forms, hideable columns, row actions, selection, pagination, and package-owned Data Table slots.
Payments
1 of 5 row(s) selected.
import { Badge } from 'radcn/badge'
import { Button } from 'radcn/button'
import { Checkbox } from 'radcn/checkbox'
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuTrigger } from 'radcn/dropdown-menu'
import {
DataTable,
DataTableBody,
DataTableCell,
DataTableColumnControls,
DataTableContent,
DataTableEmpty,
DataTableFilter,
DataTableHeader,
DataTableHeaderCell,
DataTablePagination,
DataTableRow,
DataTableRowActions,
DataTableSelectionSummary,
DataTableToolbar,
} from 'radcn/data-table'
import { Input } from 'radcn/input'
const payments = [
{ id: 'm5gr84i9', status: 'success', statusLabel: 'Success', email: 'ken99@example.com', amount: '$316.00', selected: true },
{ id: '3u1reuv4', status: 'success', statusLabel: 'Success', email: 'abe45@example.com', amount: '$242.00' },
{ id: 'derv1ws0', status: 'processing', statusLabel: 'Processing', email: 'monserrat44@example.com', amount: '$837.00' },
{ id: '5kma53ae', status: 'success', statusLabel: 'Success', email: 'silas22@example.com', amount: '$874.00' },
{ id: 'bhqecj4p', status: 'failed', statusLabel: 'Failed', email: 'carmella@example.com', amount: '$721.00' },
]
export function DataTablePreview() {
return (
<DataTable caption="Payments" rowCount={5} selectedCount={1}>
<form action="/payments" method="get">
<DataTableToolbar>
<DataTableFilter label="Filter emails">
<Input name="email" placeholder="Filter emails..." />
</DataTableFilter>
<DataTableColumnControls>
<DropdownMenu>
<DropdownMenuTrigger>Columns v</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem checked>Status</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem checked>Email</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem checked>Amount</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>
</DataTableColumnControls>
</DataTableToolbar>
</form>
<DataTableContent
caption="Payments"
class="overflow-hidden rounded-md border"
dense
style="overflow:hidden;border:1px solid var(--radcn-border);border-radius:var(--radcn-radius);"
>
<DataTableHeader>
<DataTableRow>
<DataTableHeaderCell>
<label>
<Checkbox name="select-all" />
<span class="sr-only">Select all</span>
</label>
</DataTableHeaderCell>
<DataTableHeaderCell>Status</DataTableHeaderCell>
<DataTableHeaderCell ariaSort="ascending" href="/payments?sort=email">Email ^</DataTableHeaderCell>
<DataTableHeaderCell style="text-align:right">Amount</DataTableHeaderCell>
<DataTableHeaderCell style="text-align:right">Actions</DataTableHeaderCell>
</DataTableRow>
</DataTableHeader>
<DataTableBody>
{payments.map((payment) => (
<DataTableRow selected={payment.selected}>
<DataTableCell>
<label>
<Checkbox checked={payment.selected} name="rows" value={payment.id} />
<span class="sr-only">Select row</span>
</label>
</DataTableCell>
<DataTableCell><Badge variant={payment.status === 'failed' ? 'destructive' : 'secondary'}>{payment.statusLabel}</Badge></DataTableCell>
<DataTableCell class="lowercase">{payment.email}</DataTableCell>
<DataTableCell style="text-align:right;font-weight:500">{payment.amount}</DataTableCell>
<DataTableCell>
<DropdownMenu>
<DropdownMenuTrigger ariaLabel="Open menu">...</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Copy payment ID</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>
</DataTableCell>
</DataTableRow>
))}
<DataTableEmpty colSpan={5}>No results.</DataTableEmpty>
</DataTableBody>
</DataTableContent>
<DataTablePagination page={1} pageCount={2}>
<DataTableSelectionSummary rowCount={5} selectedCount={1}>1 of 5 row(s) selected.</DataTableSelectionSummary>
<Button disabled size="sm" variant="outline">Previous</Button>
<Button size="sm" variant="outline">Next</Button>
</DataTablePagination>
</DataTable>
)
}Installation
Intended future install command. RadCN is private and not published to npm yet, so this snippet documents the target user-facing API rather than something external consumers can run today.
pnpm add radcn # intended future package
import { DataTable, DataTableContent, DataTableToolbar } from 'radcn/data-table'Theming
RadCN tokens read the resolved theme from the document. Store the user's preference separately, then resolve system preferences to a concrete light or dark theme before setting package tokens.
<html data-radcn-theme-mode="system" data-radcn-theme="dark">
...
</html>Accessibility
- Keeps the core output as a semantic table with captions, table headers, table rows, and table cells.
- Uses aria-sort on sortable header cells and real links/forms for server-owned filtering, sorting, and pagination.
- Selection is represented by native checkbox controls and visible summary text rather than hidden client state.
Customization
- Exposes data-radcn-data-table, toolbar, filter, column-controls, content, row, selection-summary, pagination, row-actions, detail, and empty hooks.
- Data operations remain explicit in route state, query strings, and submitted form values so apps can use any data layer.
Remix 3 Notes
- use client, React state, useReactTable, ColumnDef, row models, and flexRender map to explicit server state, query strings, native controls, and package-owned Data Table slots.
- TanStack Table remains an app choice, not a RadCN dependency.
- Column filters, sorting, column visibility, row selection, pagination, and row actions are app-owned controls built from native forms, links, checkboxes, and existing RadCN primitives.
- Button, Checkbox, DropdownMenu, Input, and Table composition maps to existing RadCN packages.
- className and Tailwind utilities map to class, style, RadCN package classes, CSS variables, and app CSS.
- lucide ArrowUpDown, ChevronDown, and MoreHorizontal map to app-owned icon presentation.
- navigator.clipboard.writeText stays app-owned browser behavior around visible payment id data.
- Dashboard-only drag/reorder and chart detail patterns stay as recipe/block composition until a later experiment proves a reusable package behavior is needed.
- vendor source remains read-only evidence and is not imported by RadCN.
