Component

Table

A shadcn-compatible table with TanStack sorting, resizing, row selection, virtualization, growth-based widths, and full-header sorting. It also exports the same primitive parts as shadcn table.

npx shadcn@latest add https://ui.emstein.com/r/table.json

Preview

ID
User
Role
Team
Status
Last seen
Score
#001Ari Cohenari.cohen1@example.comEngineerProductActive2026-05-0159
#002Maya Cohenmaya.cohen2@example.comManagerDataPaused2026-05-0266
#003Noah Cohennoah.cohen3@example.comAnalystGrowthPending2026-05-0373
#004Leah Cohenleah.cohen4@example.comResearcherPlatformActive2026-05-0480
#005Eli Coheneli.cohen5@example.comDesignerSupportPaused2026-05-0587
#006Nina Cohennina.cohen6@example.comEngineerProductPending2026-05-0694
#007Sam Cohensam.cohen7@example.comManagerDataActive2026-05-0752
#008Iris Coheniris.cohen8@example.comAnalystGrowthPaused2026-05-0859
#009Theo Cohentheo.cohen9@example.comResearcherPlatformPending2026-05-0966
#010Zoe Cohenzoe.cohen10@example.comDesignerSupportActive2026-05-1073
#011Ari Reedari.reed11@example.comEngineerProductPaused2026-05-1180
#012Maya Reedmaya.reed12@example.comManagerDataPending2026-05-1287
#013Noah Reednoah.reed13@example.comAnalystGrowthActive2026-05-1394
#014Leah Reedleah.reed14@example.comResearcherPlatformPaused2026-05-1452
#015Eli Reedeli.reed15@example.comDesignerSupportPending2026-05-1559
#016Nina Reednina.reed16@example.comEngineerProductActive2026-05-1666
#017Sam Reedsam.reed17@example.comManagerDataPaused2026-05-1773
#018Iris Reediris.reed18@example.comAnalystGrowthPending2026-05-1880
#019Theo Reedtheo.reed19@example.comResearcherPlatformActive2026-05-1987
#020Zoe Reedzoe.reed20@example.comDesignerSupportPaused2026-05-2094
#021Ari Stoneari.stone21@example.comEngineerProductPending2026-05-2152
#022Maya Stonemaya.stone22@example.comManagerDataActive2026-05-2259
#023Noah Stonenoah.stone23@example.comAnalystGrowthPaused2026-05-2366
#024Leah Stoneleah.stone24@example.comResearcherPlatformPending2026-05-2473
#025Eli Stoneeli.stone25@example.comDesignerSupportActive2026-05-2580
#026Nina Stonenina.stone26@example.comEngineerProductPaused2026-05-2687
#027Sam Stonesam.stone27@example.comManagerDataPending2026-05-2794
Usage
Use a simple column config instead of raw TanStack columns.
import { Table } from "@/components/emstein-ui/table"

const columns = [
  { key: "id", label: "ID", type: "shortText", width: 96 },
  { key: "user", label: "User", type: "longText", grow: 2 },
  { key: "score", label: "Score", type: "number", align: "right" },
]

export function UsersTable({ users }) {
  return (
    <Table
      data={users}
      columns={columns}
      getRowId={(row) => row.id}
    />
  )
}
Primitive usage
Use the same exported parts as shadcn table when you need full markup control.
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/emstein-ui/table"

export function InvoicesTable() {
  return (
    <Table>
      <TableCaption>A list of invoices.</TableCaption>
      <TableHeader>
        <TableRow>
          <TableHead>Invoice</TableHead>
          <TableHead className="text-right">Amount</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        <TableRow>
          <TableCell>INV001</TableCell>
          <TableCell className="text-right">$250.00</TableCell>
        </TableRow>
      </TableBody>
      <TableFooter>
        <TableRow>
          <TableCell>Total</TableCell>
          <TableCell className="text-right">$250.00</TableCell>
        </TableRow>
      </TableFooter>
    </Table>
  )
}
Column widths
Presets handle common column sizes, and grow controls how extra space is shared.
const columns = [
  { key: "name", label: "Name", type: "text" },
  { key: "description", label: "Description", type: "longText", grow: 3 },
  { key: "net", label: "Net", type: "currency", align: "right", grow: false },
]

// Extra space is shared by growable columns.
// Too little space keeps min widths and uses horizontal scroll.
Footer
Add footer cells to advanced columns for totals or summary values.
const columns = [
  { key: "collected", label: "Collected", type: "currency", footer: "$1,301.00" },
  { key: "cost", label: "Cost", type: "currency", footer: "$726.55" },
  {
    key: "net",
    label: "Net",
    type: "currency",
    align: "right",
    footer: ({ rows }) =>
      rows.reduce((total, row) => total + row.net, 0).toLocaleString(),
  },
]
Selection
Selection is controlled when you pass selectedRows.
const [selectedRows, setSelectedRows] = React.useState<string[]>([])

<Table
  data={users}
  columns={columns}
  getRowId={(row) => row.id}
  selection={{
    selectedRows,
    onSelectedRowsChange: setSelectedRows,
  }}
/>

Header behavior

Hovering a header shows the available resize dividers. Sortable columns can be sorted by clicking anywhere inside the header cell. Active sorting toggles between ascending and descending.

Props

dataRows to render.
columnsSimple column config with key, label, type, width, render, and align.
column.typeWidth preset: text, longText, shortText, number, currency, date, status, or actions.
column.growControls how extra container width is shared. Text presets grow by default. Use false to keep a column compact.
column.widthManual width override. Wins over the type preset.
column.minWidthManual minimum width override. Wins over the type preset.
column.footerFooter cell content. Pass a value or a function using data and rendered rows.
selectionAdds checkbox selection. Pass selectedRows and onSelectedRowsChange.
resizableEnables column resizing. Default: true.
sortableEnables sorting. Default: true.
showUnsortedSortIconShows a muted chevron on sortable unsorted columns.
initialSortDefaults to the first sortable column ascending. Use false to start unsorted.
scrollModeUse page scroll by default, or table scroll with scrollMode='table'.
captionOptional caption for the advanced data table.