Table
freeTable gives dense records a scannable grid you compose from styled row and cell primitives.
| Member | Role | Status |
|---|---|---|
Aria Patel aria@acme.io | Owner | Active |
Diego Ramos diego@acme.io | Admin | Active |
Mei Lin mei@acme.io | Editor | Invited |
Sam Turner sam@acme.io | Viewer | Active |
import Table from '@/components/ui/Table';
import Avatar from '@/components/ui/Avatar';
import Tag from '@/components/ui/Tag';
const { THead, TBody, Tr, Th, Td } = Table;
const members = [
{
id: 1,
name: 'Aria Patel',
email: 'aria@acme.io',
avatar: '/img/avatars/thumb-1.jpg',
role: 'Owner',
status: 'Active',
},
{
id: 2,
name: 'Diego Ramos',
email: 'diego@acme.io',
avatar: '/img/avatars/thumb-2.jpg',
role: 'Admin',
status: 'Active',
},
{
id: 3,
name: 'Mei Lin',
email: 'mei@acme.io',
avatar: '/img/avatars/thumb-3.jpg',
role: 'Editor',
status: 'Invited',
},
{
id: 4,
name: 'Sam Turner',
email: 'sam@acme.io',
avatar: '/img/avatars/thumb-4.jpg',
role: 'Viewer',
status: 'Active',
},
];
export default function UsageDemo() {
return (
<Table>
<THead>
<Tr>
<Th>Member</Th>
<Th>Role</Th>
<Th>Status</Th>
</Tr>
</THead>
<TBody>
{members.map((member) => (
<Tr key={member.id}>
<Td>
<div className="flex items-center gap-2">
<Avatar
size={32}
src={member.avatar}
alt={member.name}
/>
<div className="min-w-0">
<div className="font-semibold text-foreground">
{member.name}
</div>
<div className="text-muted-foreground">
{member.email}
</div>
</div>
</div>
</Td>
<Td>{member.role}</Td>
<Td>
<Tag className="gap-1 bg-card">
<span
className={`h-2 w-2 rounded-full ${
member.status === 'Active' ? 'bg-success' :
member.status === 'Invited' ? 'bg-warning' : ''
}`}
/>
{member.status}
</Tag>
</Td>
</Tr>
))}
</TBody>
</Table>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add TableExamples
Basic
| Invoice | Client | Amount |
|---|---|---|
| INV-1024 | Northwind Traders | $1,280.00 |
| INV-1025 | Globex Corp | $3,540.00 |
| INV-1026 | Soylent Industries | $920.00 |
import Table from '@/components/ui/Table';
const { THead, TBody, Tr, Th, Td } = Table;
const invoices = [
{ id: 'INV-1024', client: 'Northwind Traders', amount: '$1,280.00' },
{ id: 'INV-1025', client: 'Globex Corp', amount: '$3,540.00' },
{ id: 'INV-1026', client: 'Soylent Industries', amount: '$920.00' },
];
export default function BasicDemo() {
return (
<Table>
<THead>
<Tr>
<Th>Invoice</Th>
<Th>Client</Th>
<Th>Amount</Th>
</Tr>
</THead>
<TBody>
{invoices.map((invoice) => (
<Tr key={invoice.id}>
<Td className="font-medium text-foreground">{invoice.id}</Td>
<Td>{invoice.client}</Td>
<Td className="tabular-nums">{invoice.amount}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Compact
| SKU | Product | In stock |
|---|---|---|
| KB-01 | Mechanical Keyboard | 132 |
| MS-04 | Wireless Mouse | 58 |
| HS-02 | Studio Headset | 9 |
| WC-06 | HD Webcam | 0 |
| DK-03 | Docking Station | 24 |
import Table from '@/components/ui/Table';
const { THead, TBody, Tr, Th, Td } = Table;
const rows = [
{ sku: 'KB-01', name: 'Mechanical Keyboard', stock: 132 },
{ sku: 'MS-04', name: 'Wireless Mouse', stock: 58 },
{ sku: 'HS-02', name: 'Studio Headset', stock: 9 },
{ sku: 'WC-06', name: 'HD Webcam', stock: 0 },
{ sku: 'DK-03', name: 'Docking Station', stock: 24 },
];
export default function CompactDemo() {
return (
<Table compact>
<THead>
<Tr>
<Th>SKU</Th>
<Th>Product</Th>
<Th>In stock</Th>
</Tr>
</THead>
<TBody>
{rows.map((row) => (
<Tr key={row.sku}>
<Td className="font-mono text-xs">{row.sku}</Td>
<Td className="font-medium text-foreground">{row.name}</Td>
<Td className="tabular-nums">{row.stock}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Bordered
| Project | Lead | Progress |
|---|---|---|
| Website relaunch | Aria Patel | 80% |
| Mobile app v2 | Diego Ramos | 45% |
| Billing migration | Mei Lin | 12% |
import Table from '@/components/ui/Table';
const { THead, TBody, Tr, Th, Td } = Table;
const rows = [
{ project: 'Website relaunch', lead: 'Aria Patel', progress: '80%' },
{ project: 'Mobile app v2', lead: 'Diego Ramos', progress: '45%' },
{ project: 'Billing migration', lead: 'Mei Lin', progress: '12%' },
];
export default function BorderedDemo() {
return (
<Table overflow={false} bordered>
<THead>
<Tr>
<Th>Project</Th>
<Th>Lead</Th>
<Th>Progress</Th>
</Tr>
</THead>
<TBody>
{rows.map((row) => (
<Tr key={row.project}>
<Td className="font-medium text-foreground">{row.project}</Td>
<Td>{row.lead}</Td>
<Td className="tabular-nums">{row.progress}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Vertical Divider
| Region | Deals | Revenue |
|---|---|---|
| North America | 42 | $128k |
| Europe | 31 | $96k |
| Asia Pacific | 27 | $74k |
import Table from '@/components/ui/Table';
const { THead, TBody, Tr, Th, Td } = Table;
const rows = [
{ region: 'North America', deals: 42, revenue: '$128k' },
{ region: 'Europe', deals: 31, revenue: '$96k' },
{ region: 'Asia Pacific', deals: 27, revenue: '$74k' },
];
export default function VerticalDividerDemo() {
return (
<Table verticalDivider={{ head: true, body: true }}>
<THead>
<Tr>
<Th>Region</Th>
<Th>Deals</Th>
<Th>Revenue</Th>
</Tr>
</THead>
<TBody>
{rows.map((row) => (
<Tr key={row.region}>
<Td className="font-medium text-foreground">{row.region}</Td>
<Td className="tabular-nums">{row.deals}</Td>
<Td className="tabular-nums">{row.revenue}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Grouped Header
| Rep | Meetings | Outcomes | ||
|---|---|---|---|---|
| Q1 | Q2 | Won | Lost | |
| Aria Patel | 24 | 31 | 12 | 4 |
| Diego Ramos | 18 | 22 | 9 | 7 |
| Mei Lin | 30 | 27 | 15 | 3 |
import Table from '@/components/ui/Table';
const { THead, TBody, Tr, Th, Td } = Table;
const rows = [
{ name: 'Aria Patel', q1: 24, q2: 31, won: 12, lost: 4 },
{ name: 'Diego Ramos', q1: 18, q2: 22, won: 9, lost: 7 },
{ name: 'Mei Lin', q1: 30, q2: 27, won: 15, lost: 3 },
];
export default function GroupedHeaderDemo() {
return (
<Table verticalDivider={{ head: true }}>
<THead>
<Tr>
<Th rowSpan={2}>Rep</Th>
<Th colSpan={2} className="text-center">
Meetings
</Th>
<Th colSpan={2} className="text-center">
Outcomes
</Th>
</Tr>
<Tr>
<Th>Q1</Th>
<Th>Q2</Th>
<Th>Won</Th>
<Th>Lost</Th>
</Tr>
</THead>
<TBody>
{rows.map((row) => (
<Tr key={row.name}>
<Td className="font-medium text-foreground">{row.name}</Td>
<Td className="tabular-nums">{row.q1}</Td>
<Td className="tabular-nums">{row.q2}</Td>
<Td className="tabular-nums">{row.won}</Td>
<Td className="tabular-nums">{row.lost}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Sorting
| ORD-3014 | $960 | 7 |
| ORD-3012 | $420 | 3 |
| ORD-3015 | $275 | 2 |
| ORD-3013 | $180 | 1 |
import { useState } from 'react';
import Table from '@/components/ui/Table';
const { THead, TBody, Tr, Th, Td, Sorter } = Table;
type Order = { id: string; total: number; items: number };
const orders: Order[] = [
{ id: 'ORD-3012', total: 420, items: 3 },
{ id: 'ORD-3013', total: 180, items: 1 },
{ id: 'ORD-3014', total: 960, items: 7 },
{ id: 'ORD-3015', total: 275, items: 2 },
];
type SortKey = keyof Order;
type SortState = { key: SortKey; order: 'asc' | 'desc' } | null;
export default function SortingDemo() {
const [sort, setSort] = useState<SortState>({ key: 'total', order: 'desc' });
const sorted = [...orders].sort((a, b) => {
if (!sort) return 0;
const first = a[sort.key];
const second = b[sort.key];
const diff =
typeof first === 'number' && typeof second === 'number'
? first - second
: String(first).localeCompare(String(second));
return sort.order === 'asc' ? diff : -diff;
});
const toggleSort = (key: SortKey) => {
setSort((prev) =>
prev && prev.key === key
? { key, order: prev.order === 'asc' ? 'desc' : 'asc' }
: { key, order: 'asc' },
);
};
const sortValue = (key: SortKey) =>
sort && sort.key === key ? sort.order : true;
const columns: [SortKey, string][] = [
['id', 'Order'],
['total', 'Total'],
['items', 'Items'],
];
return (
<Table>
<THead>
<Tr>
{columns.map(([key, label]) => (
<Th key={key}>
<button
type="button"
className="inline-flex cursor-pointer items-center gap-2 uppercase"
onClick={() => toggleSort(key)}
>
{label}
<Sorter sort={sortValue(key)} />
</button>
</Th>
))}
</Tr>
</THead>
<TBody>
{sorted.map((order) => (
<Tr key={order.id}>
<Td className="font-medium text-foreground">{order.id}</Td>
<Td className="tabular-nums">${order.total}</Td>
<Td className="tabular-nums">{order.items}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Filtering
| Name | Team |
|---|---|
| Aria Patel | Design |
| Diego Ramos | Engineering |
| Mei Lin | Engineering |
| Sam Turner | Support |
| Priya Shah | Design |
import { useState } from 'react';
import Table from '@/components/ui/Table';
import Input from '@/components/ui/Input';
const { THead, TBody, Tr, Th, Td } = Table;
const people = [
{ id: 1, name: 'Aria Patel', team: 'Design' },
{ id: 2, name: 'Diego Ramos', team: 'Engineering' },
{ id: 3, name: 'Mei Lin', team: 'Engineering' },
{ id: 4, name: 'Sam Turner', team: 'Support' },
{ id: 5, name: 'Priya Shah', team: 'Design' },
];
export default function FilteringDemo() {
const [query, setQuery] = useState('');
const term = query.trim().toLowerCase();
const filtered = people.filter((person) => {
const haystack = (person.name + ' ' + person.team).toLowerCase();
return haystack.includes(term);
});
return (
<div className="flex w-full flex-col gap-4">
<div className="px-4 pt-4">
<Input
value={query}
placeholder="Search people..."
onChange={(event) => setQuery(event.target.value)}
/>
</div>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Team</Th>
</Tr>
</THead>
<TBody>
{filtered.length > 0 ? (
filtered.map((person) => (
<Tr key={person.id}>
<Td className="font-medium text-foreground">{person.name}</Td>
<Td>{person.team}</Td>
</Tr>
))
) : (
<Tr>
<Td colSpan={2} className="text-center text-muted-foreground">
No people match the search.
</Td>
</Tr>
)}
</TBody>
</Table>
</div>
);
}Row Selection
| File | Size | |
|---|---|---|
| Q3-report.pdf | 2.4 MB | |
| brand-assets.zip | 18 MB | |
| roadmap.png | 640 KB | |
| contract.docx | 82 KB |
import { useState } from 'react';
import Table from '@/components/ui/Table';
import Checkbox from '@/components/ui/Checkbox';
const { THead, TBody, Tr, Th, Td } = Table;
const files = [
{ id: 'f1', name: 'Q3-report.pdf', size: '2.4 MB' },
{ id: 'f2', name: 'brand-assets.zip', size: '18 MB' },
{ id: 'f3', name: 'roadmap.png', size: '640 KB' },
{ id: 'f4', name: 'contract.docx', size: '82 KB' },
];
export default function RowSelectionDemo() {
const [selected, setSelected] = useState<Set<string>>(new Set(['f2']));
const allSelected = selected.size === files.length;
const someSelected = selected.size > 0 && !allSelected;
const toggleAll = () => {
if (allSelected) {
setSelected(new Set());
} else {
setSelected(new Set(files.map((file) => file.id)));
}
};
const toggleRow = (id: string) => {
setSelected((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
};
return (
<Table>
<THead>
<Tr>
<Th className="w-10">
<Checkbox
checked={allSelected}
indeterminate={someSelected}
onChange={toggleAll}
/>
</Th>
<Th>File</Th>
<Th>Size</Th>
</Tr>
</THead>
<TBody>
{files.map((file) => (
<Tr
key={file.id}
className={selected.has(file.id) ? 'bg-primary-soft' : undefined}
>
<Td>
<Checkbox
checked={selected.has(file.id)}
onChange={() => toggleRow(file.id)}
/>
</Td>
<Td className="font-medium text-foreground">{file.name}</Td>
<Td className="tabular-nums text-muted-foreground">{file.size}</Td>
</Tr>
))}
</TBody>
</Table>
);
}Pagination
| Reference | Amount |
|---|---|
| TXN-5000 | $42.00 |
| TXN-5001 | $79.00 |
| TXN-5002 | $116.00 |
| TXN-5003 | $153.00 |
| TXN-5004 | $190.00 |
- 1
- 2
- 3
- 4
- 5
import { useState } from 'react';
import Table from '@/components/ui/Table';
import Pagination from '@/components/ui/Pagination';
const { THead, TBody, Tr, Th, Td } = Table;
const transactions = Array.from({ length: 23 }, (_, index) => ({
id: 5000 + index,
reference: 'TXN-' + (5000 + index),
amount: '$' + (index * 37 + 42).toFixed(2),
}));
const PAGE_SIZE = 5;
export default function PaginationDemo() {
const [page, setPage] = useState(1);
const start = (page - 1) * PAGE_SIZE;
const pageRows = transactions.slice(start, start + PAGE_SIZE);
return (
<div className="flex w-full flex-col gap-4">
<Table>
<THead>
<Tr>
<Th>Reference</Th>
<Th>Amount</Th>
</Tr>
</THead>
<TBody>
{pageRows.map((transaction) => (
<Tr key={transaction.id}>
<Td className="font-medium text-foreground">
{transaction.reference}
</Td>
<Td className="tabular-nums">{transaction.amount}</Td>
</Tr>
))}
</TBody>
</Table>
<div className="flex items-center justify-between px-4 pb-4">
<span className="text-sm text-muted-foreground">
{transactions.length} transactions
</span>
<Pagination
currentPage={page}
total={transactions.length}
pageSize={PAGE_SIZE}
onChange={setPage}
/>
</div>
</div>
);
}Expandable Rows
| Shipment | Carrier | Status | |
|---|---|---|---|
| SHP-01 | FedEx | In transit | |
| |||
| SHP-02 | UPS | Delivered | |
| SHP-03 | DHL | Pending | |
import { Fragment, useState } from 'react';
import Table from '@/components/ui/Table';
import { PiCaretRight } from 'react-icons/pi'
const { THead, TBody, Tr, Th, Td } = Table;
const shipments = [
{
id: 'SHP-01',
carrier: 'FedEx',
status: 'In transit',
events: ['Picked up - Austin, TX', 'Departed - Memphis, TN'],
},
{
id: 'SHP-02',
carrier: 'UPS',
status: 'Delivered',
events: ['Picked up - Denver, CO', 'Delivered - Boulder, CO'],
},
{
id: 'SHP-03',
carrier: 'DHL',
status: 'Pending',
events: ['Label created - Newark, NJ'],
},
];
export default function ExpandableRowsDemo() {
const [open, setOpen] = useState<Set<string>>(new Set(['SHP-01']));
const toggle = (id: string) => {
setOpen((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
};
return (
<Table>
<THead>
<Tr>
<Th className="w-10" />
<Th>Shipment</Th>
<Th>Carrier</Th>
<Th>Status</Th>
</Tr>
</THead>
<TBody>
{shipments.map((shipment) => {
const isOpen = open.has(shipment.id);
return (
<Fragment key={shipment.id}>
<Tr>
<Td>
<button
type="button"
aria-label={isOpen ? 'Collapse row' : 'Expand row'}
className="inline-flex cursor-pointer text-muted-foreground"
onClick={() => toggle(shipment.id)}
>
<PiCaretRight
className={isOpen ? 'rotate-90 transition' : 'transition'}
/>
</button>
</Td>
<Td className="font-medium text-foreground">{shipment.id}</Td>
<Td>{shipment.carrier}</Td>
<Td className="text-muted-foreground">{shipment.status}</Td>
</Tr>
{isOpen && (
<Tr>
<Td></Td>
<Td colSpan={3}>
<ul className="flex flex-col gap-2">
{shipment.events.map((event) => (
<li key={event}>{event}</li>
))}
</ul>
</Td>
</Tr>
)}
</Fragment>
);
})}
</TBody>
</Table>
);
}TanStack Table
Use Table as the styled shell around a headless table model when you need
richer data behavior. This example pairs the table primitives with
@tanstack/react-table: TanStack owns the column
definitions, row model, sorting, and pagination state, while NateUI renders the
native table structure, Sorter, Pagination, and Select controls.
Ticket | Subject | Priority | Updated |
|---|---|---|---|
| TCK-201 | Checkout button unresponsive | High | 2h ago |
| TCK-198 | Export CSV missing a column | Medium | 5h ago |
| TCK-195 | Typo in confirmation email | Low | 1d ago |
| TCK-190 | Slow load on reports page | Medium | 2d ago |
| TCK-187 | Dark mode toggle resets on reload | Medium | 3d ago |
- 1
- 2
- 3
import { useMemo, useState } from 'react';
import {
columnVisibilityFeature,
createPaginatedRowModel,
createSortedRowModel,
flexRender,
rowPaginationFeature,
rowSortingFeature,
tableFeatures,
useTable,
} from '@tanstack/react-table';
import type { ColumnDef, ColumnSort } from '@tanstack/react-table';
import Table from '@/components/ui/Table';
import Pagination from '@/components/ui/Pagination';
import Select from '@/components/ui/Select';
import Tag from '@/components/ui/Tag';
const { THead, TBody, Tr, Th, Td, Sorter } = Table;
type Ticket = {
id: string;
subject: string;
priority: 'Low' | 'Medium' | 'High';
updated: string;
};
const tickets: Ticket[] = [
{ id: 'TCK-201', subject: 'Checkout button unresponsive', priority: 'High', updated: '2h ago' },
{ id: 'TCK-198', subject: 'Export CSV missing a column', priority: 'Medium', updated: '5h ago' },
{ id: 'TCK-195', subject: 'Typo in confirmation email', priority: 'Low', updated: '1d ago' },
{ id: 'TCK-190', subject: 'Slow load on reports page', priority: 'Medium', updated: '2d ago' },
{ id: 'TCK-187', subject: 'Dark mode toggle resets on reload', priority: 'Medium', updated: '3d ago' },
{ id: 'TCK-183', subject: 'API rate limit hit during import', priority: 'High', updated: '4d ago' },
{ id: 'TCK-179', subject: 'Invite link expires too early', priority: 'Low', updated: '5d ago' },
{ id: 'TCK-174', subject: 'Duplicate rows after CSV export', priority: 'High', updated: '6d ago' },
{ id: 'TCK-168', subject: 'Missing empty state on filters', priority: 'Low', updated: '1w ago' },
{ id: 'TCK-161', subject: 'Timezone off by one hour', priority: 'Medium', updated: '1w ago' },
{ id: 'TCK-155', subject: 'Search ignores accented characters', priority: 'Low', updated: '2w ago' },
{ id: 'TCK-149', subject: 'Webhook retries never stop', priority: 'High', updated: '2w ago' },
];
const features = tableFeatures({
columnVisibilityFeature,
rowPaginationFeature,
rowSortingFeature,
paginatedRowModel: createPaginatedRowModel(),
sortedRowModel: createSortedRowModel(),
});
const columns: ColumnDef<typeof features, Ticket>[] = [
{ accessorKey: 'id', header: 'Ticket' },
{ accessorKey: 'subject', header: 'Subject' },
{
accessorKey: 'priority',
header: 'Priority',
cell: ({ row }) => {
const { priority } = row.original;
return (
<Tag
prefix
prefixClass={
priority === 'High'
? 'bg-destructive'
: priority === 'Medium'
? 'bg-warning'
: undefined
}
className="bg-card"
>
{priority}
</Tag>
);
},
},
{ accessorKey: 'updated', header: 'Updated', enableSorting: false },
];
const pageSizeOptions = [5, 10, 20].map((value) => ({
value,
label: `${value} / page`,
}));
export default function TanStackTableDemo() {
const [sorting, setSorting] = useState<ColumnSort[]>([]);
const [pageIndex, setPageIndex] = useState(1);
const [pageSize, setPageSize] = useState(5);
const sorted = useMemo(() => {
if (sorting.length === 0) return tickets;
const { id, desc } = sorting[0];
return [...tickets].sort((a, b) => {
const diff = String(a[id as keyof Ticket]).localeCompare(
String(b[id as keyof Ticket]),
);
return desc ? -diff : diff;
});
}, [sorting]);
const start = (pageIndex - 1) * pageSize;
const pageRows = sorted.slice(start, start + pageSize);
const table = useTable({
features,
data: pageRows,
columns,
manualSorting: true,
manualPagination: true,
state: { sorting },
onSortingChange: (updater) => {
const next = typeof updater === 'function' ? updater(sorting) : updater;
setSorting(next);
setPageIndex(1);
},
});
return (
<div className="flex flex-col gap-4 w-full">
<Table>
<THead>
{table.getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th
key={header.id}
className={
header.column.getCanSort()
? 'cursor-pointer select-none'
: undefined
}
onClick={header.column.getToggleSortingHandler()}
>
<div className="inline-flex items-center gap-1">
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{header.column.getCanSort() && (
<Sorter sort={header.column.getIsSorted()} />
)}
</div>
</Th>
))}
</Tr>
))}
</THead>
<TBody>
{table.getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
))}
</Tr>
))}
</TBody>
</Table>
<div className="flex items-center justify-between px-4 pb-4">
<Pagination
currentPage={pageIndex}
total={tickets.length}
pageSize={pageSize}
onChange={setPageIndex}
/>
<Select
size="sm"
className="min-w-30"
placement="top"
isSearchable={false}
value={pageSizeOptions.find((option) => option.value === pageSize)}
options={pageSizeOptions}
onChange={(option) => {
setPageSize(option?.value ?? pageSize);
setPageIndex(1);
}}
/>
</div>
</div>
);
}API
Table is a compound component: the default export is the styling wrapper, and
the section, row, and cell primitives hang off it as Table.THead,
Table.TBody, Table.TFoot, Table.Tr, Table.Th, Table.Td, and
Table.Sorter. Each primitive renders its matching native element and forwards
native props, so you own the data and behavior yourself.
Table
| Prop | Description | Type | Default |
|---|---|---|---|
bordered | Wrap the table in a bordered card surface. Only applies when overflow is false. | boolean | false |
compact | Reduce cell padding for dense data. | boolean | false |
hoverable | Highlight body rows on hover. | boolean | true |
overflow | Wrap the table in a horizontal scroll container. | boolean | true |
overflowClass | Extra classes for the overflow container. | string | - |
verticalDivider | Show vertical column dividers per section. | TableVerticalDivider | { head: false, body: false, footer: false } |
asElement | Render the table as a different element, for example a flex layout. | ElementType | 'table' |
...nativeProps | Native table element props. | ComponentPropsWithRef<'table'> | - |
Table.THead / Table.TBody / Table.TFoot
| Prop | Description | Type | Default |
|---|---|---|---|
asElement | Render the section as a different element. | ElementType | 'thead' / 'tbody' / 'tfoot' |
...nativeProps | Native section element props. | ComponentPropsWithRef<'thead'> | - |
Table.Tr
| Prop | Description | Type | Default |
|---|---|---|---|
asElement | Render the row as a different element. | ElementType | 'tr' |
...nativeProps | Native tr props, including className for selected or active rows. | ComponentPropsWithRef<'tr'> | - |
Table.Th / Table.Td
| Prop | Description | Type | Default |
|---|---|---|---|
asElement | Render the cell as a different element. | ElementType | 'th' / 'td' |
...nativeProps | Native cell props, including colSpan, rowSpan, and className. | ComponentPropsWithRef<'th'> | - |
Table.Sorter
| Prop | Description | Type | Default |
|---|---|---|---|
sort | Current sort direction. true shows the neutral indicator; 'asc' and 'desc' show the active arrows. | boolean | 'asc' | 'desc' | - |
className | Extra classes for the sorter wrapper. | string | - |
Types
type TableVerticalDivider = {
head?: boolean
body?: boolean
footer?: boolean
}