RadCN
Blocksreadyblockready

Data Table

A package-backed Data Table composition for sortable, filterable, selectable, paginated, and action-oriented table screens.

Importimport { DataTable, DataTableContent, DataTableToolbar } from 'radcn/data-table'
Preview

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.

Preview

Payments

Payments
StatusEmail AmountActions
Successken99@example.com$316.00
Successabe45@example.com$242.00
Processingmonserrat44@example.com$837.00
Successsilas22@example.com$874.00
Failedcarmella@example.com$721.00
No results.

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.