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.jsonPreview
ID | User | Role | Team | Status | Last seen | Score |
|---|---|---|---|---|---|---|
| #001 | Ari Cohenari.cohen1@example.com | Engineer | Product | Active | 2026-05-01 | 59 |
| #002 | Maya Cohenmaya.cohen2@example.com | Manager | Data | Paused | 2026-05-02 | 66 |
| #003 | Noah Cohennoah.cohen3@example.com | Analyst | Growth | Pending | 2026-05-03 | 73 |
| #004 | Leah Cohenleah.cohen4@example.com | Researcher | Platform | Active | 2026-05-04 | 80 |
| #005 | Eli Coheneli.cohen5@example.com | Designer | Support | Paused | 2026-05-05 | 87 |
| #006 | Nina Cohennina.cohen6@example.com | Engineer | Product | Pending | 2026-05-06 | 94 |
| #007 | Sam Cohensam.cohen7@example.com | Manager | Data | Active | 2026-05-07 | 52 |
| #008 | Iris Coheniris.cohen8@example.com | Analyst | Growth | Paused | 2026-05-08 | 59 |
| #009 | Theo Cohentheo.cohen9@example.com | Researcher | Platform | Pending | 2026-05-09 | 66 |
| #010 | Zoe Cohenzoe.cohen10@example.com | Designer | Support | Active | 2026-05-10 | 73 |
| #011 | Ari Reedari.reed11@example.com | Engineer | Product | Paused | 2026-05-11 | 80 |
| #012 | Maya Reedmaya.reed12@example.com | Manager | Data | Pending | 2026-05-12 | 87 |
| #013 | Noah Reednoah.reed13@example.com | Analyst | Growth | Active | 2026-05-13 | 94 |
| #014 | Leah Reedleah.reed14@example.com | Researcher | Platform | Paused | 2026-05-14 | 52 |
| #015 | Eli Reedeli.reed15@example.com | Designer | Support | Pending | 2026-05-15 | 59 |
| #016 | Nina Reednina.reed16@example.com | Engineer | Product | Active | 2026-05-16 | 66 |
| #017 | Sam Reedsam.reed17@example.com | Manager | Data | Paused | 2026-05-17 | 73 |
| #018 | Iris Reediris.reed18@example.com | Analyst | Growth | Pending | 2026-05-18 | 80 |
| #019 | Theo Reedtheo.reed19@example.com | Researcher | Platform | Active | 2026-05-19 | 87 |
| #020 | Zoe Reedzoe.reed20@example.com | Designer | Support | Paused | 2026-05-20 | 94 |
| #021 | Ari Stoneari.stone21@example.com | Engineer | Product | Pending | 2026-05-21 | 52 |
| #022 | Maya Stonemaya.stone22@example.com | Manager | Data | Active | 2026-05-22 | 59 |
| #023 | Noah Stonenoah.stone23@example.com | Analyst | Growth | Paused | 2026-05-23 | 66 |
| #024 | Leah Stoneleah.stone24@example.com | Researcher | Platform | Pending | 2026-05-24 | 73 |
| #025 | Eli Stoneeli.stone25@example.com | Designer | Support | Active | 2026-05-25 | 80 |
| #026 | Nina Stonenina.stone26@example.com | Engineer | Product | Paused | 2026-05-26 | 87 |
| #027 | Sam Stonesam.stone27@example.com | Manager | Data | Pending | 2026-05-27 | 94 |
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
| data | Rows to render. |
| columns | Simple column config with key, label, type, width, render, and align. |
| column.type | Width preset: text, longText, shortText, number, currency, date, status, or actions. |
| column.grow | Controls how extra container width is shared. Text presets grow by default. Use false to keep a column compact. |
| column.width | Manual width override. Wins over the type preset. |
| column.minWidth | Manual minimum width override. Wins over the type preset. |
| column.footer | Footer cell content. Pass a value or a function using data and rendered rows. |
| selection | Adds checkbox selection. Pass selectedRows and onSelectedRowsChange. |
| resizable | Enables column resizing. Default: true. |
| sortable | Enables sorting. Default: true. |
| showUnsortedSortIcon | Shows a muted chevron on sortable unsorted columns. |
| initialSort | Defaults to the first sortable column ascending. Use false to start unsorted. |
| scrollMode | Use page scroll by default, or table scroll with scrollMode='table'. |
| caption | Optional caption for the advanced data table. |