Table
Table is a compound component for displaying structured data in rows and columns. It supports sorting, striped rows, sticky headers, and flexible data display.
Playground
| Status | ||||
|---|---|---|---|---|
| PAY-7291 | Success | emily@example.com | Dec 20, 2030 | $316.00 |
| PAY-7292 | Success | james@example.com | Dec 20, 2030 | $242.00 |
| PAY-7293 | Pending | sarah@example.com | Dec 21, 2030 | $837.00 |
| PAY-7294 | Success | david@example.com | Dec 21, 2030 | $124.00 |
| PAY-7295 | Failed | olivia@example.com | Dec 22, 2030 | $495.00 |
| PAY-7296 | Success | michael@example.com | Dec 22, 2030 | $158.00 |
| PAY-7297 | Pending | sophie@example.com | Dec 23, 2030 | $721.00 |
| PAY-7298 | Success | daniel@example.com | Dec 24, 2030 | $89.00 |
| PAY-7299 | Failed | emma@example.com | Dec 24, 2030 | $362.00 |
| PAY-7300 | Success | ryan@example.com | Dec 25, 2030 | $550.00 |
<Table>
<Table.Header>
<Table.Row>
<Table.Head sortable sortDirection={sortKey === 'id' ? sortDir : null} onSort={() => handleSort('id')}>Invoice</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head sortable sortDirection={sortKey === 'email' ? sortDir : null} onSort={() => handleSort('email')}>Email</Table.Head>
<Table.Head sortable sortDirection={sortKey === 'date' ? sortDir : null} onSort={() => handleSort('date')}>Date</Table.Head>
<Table.Head align="right" sortable sortDirection={sortKey === 'amount' ? sortDir : null} onSort={() => handleSort('amount')}>Amount</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{sortedData.map(row => (
<Table.Row key={row.id} interactive>
<Table.Cell className="font-mono font-normal">{row.id}</Table.Cell>
<Table.Cell>
<Badge
variant="subtle"
color={statusMap[row.status].color}
size="sm"
>
{statusMap[row.status].label}
</Badge>
</Table.Cell>
<Table.Cell className="text-text-muted">{row.email}</Table.Cell>
<Table.Cell className="text-text-muted whitespace-nowrap">{row.date}</Table.Cell>
<Table.Cell align="right" className="font-mono">${row.amount.toFixed(2)}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>Variants
Default
| Name | Role | Status |
|---|---|---|
| Bob | Designer | Away |
| Alice | Engineer | Active |
<Table>
<Table.Header>
<Table.Row>
<Table.Head>Name</Table.Head>
<Table.Head>Role</Table.Head>
<Table.Head>Status</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>Bob</Table.Cell>
<Table.Cell>Designer</Table.Cell>
<Table.Cell>
<Badge variant="subtle" color="warning" size="sm">Away</Badge>
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Alice</Table.Cell>
<Table.Cell>Engineer</Table.Cell>
<Table.Cell>
<Badge variant="subtle" color="success" size="sm">Active</Badge>
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>Bordered
| Name | Role | Status |
|---|---|---|
| Bob | Designer | Away |
| Alice | Engineer | Active |
<Table variant="bordered">...</Table>Striped
| Name | Role | Status |
|---|---|---|
| Bob | Designer | Away |
| Alice | Engineer | Active |
| Charlie | PM | Meeting |
| Diana | DevOps | Active |
<Table variant="striped">...</Table>Sizes
smdefaultlg| Size | Cell PY | Cell PX | Body Font | Head Font |
|---|---|---|---|---|
sm | 8px | 12px | 14px | 12px |
default | 12px | 16px | 14px | 12px |
lg | 16px | 24px | 16px | 14px |
API
Component Structure
Table— Pure React.Header'<thead>' wrapper.Body'<tbody>' wrapper.Footer'<tfoot>' wrapper.RowPropsTable row (interactive, selected).HeadPropsHeader cell (align, sortable).CellPropsData cell (align).CaptionTable captionProps
Table
size"default""sm" | "default" | "lg"Row density (sm: compact / default: standard / lg: spacious)
variant"default""default" | "bordered" | "striped"Visual style of the table
stickyHeaderfalsebooleanFix header on scroll
wrapperClassNameundefinedstringclassName added to the scroll wrapper (e.g., "max-h-[400px]"). Use with stickyHeader
| Name | Type | Default | Description |
|---|---|---|---|
size | "sm" | "default" | "lg" | "default" | Row density (sm: compact / default: standard / lg: spacious) |
variant | "default" | "bordered" | "striped" | "default" | Visual style of the table |
stickyHeader | boolean | false | Fix header on scroll |
wrapperClassName | string | undefined | className added to the scroll wrapper (e.g., "max-h-[400px]"). Use with stickyHeader |
Table.Row
interactivefalsebooleanShow highlight effect on hover
selectedfalsebooleanIndicates row selected state (adds data-selected attribute)
| Name | Type | Default | Description |
|---|---|---|---|
interactive | boolean | false | Show highlight effect on hover |
selected | boolean | false | Indicates row selected state (adds data-selected attribute) |
Table.Head
align"left""left" | "center" | "right"Horizontal text alignment
sortablefalsebooleanShow sort indicator
sortDirectionnull"asc" | "desc" | nullCurrent sort direction
onSortundefined() => voidCallback on sort click
sortIconBuilt-in SVG{ asc?: ReactNode, desc?: ReactNode, default?: ReactNode }Customize sort icons (partial override supported)
| Name | Type | Default | Description |
|---|---|---|---|
align | "left" | "center" | "right" | "left" | Horizontal text alignment |
sortable | boolean | false | Show sort indicator |
sortDirection | "asc" | "desc" | null | null | Current sort direction |
onSort | () => void | undefined | Callback on sort click |
sortIcon | { asc?: ReactNode, desc?: ReactNode, default?: ReactNode } | Built-in SVG | Customize sort icons (partial override supported) |
Table.Cell
align"left""left" | "center" | "right"Horizontal text alignment
| Name | Type | Default | Description |
|---|---|---|---|
align | "left" | "center" | "right" | "left" | Horizontal text alignment |
Customization
sortIcon Customize sort icons freely via the sortIcon prop. Partial overrides are also supported.
Replace with Lucide icons
| Role | ||
|---|---|---|
| Bob | bob@example.com | Designer |
| Alice | alice@example.com | Engineer |
| Charlie | charlie@example.com | PM |
import { ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react'
<Table.Head
sortable
sortIcon={{
asc: <ArrowUp className="icon-xs" />,
desc: <ArrowDown className="icon-xs" />,
default: <ArrowUpDown className="icon-xs" />,
}}
>
Name
</Table.Head>Partial override
| Grade | |
|---|---|
| 95 | A+ |
| 87 | B+ |
| 72 | C+ |
// Override only asc and desc (default keeps built-in icon)
<Table.Head sortable sortIcon={{ asc: <span>↑</span>, desc: <span>↓</span> }}>
Score
</Table.Head>Tip: Use icon-xs (14px) for sort icons — the ideal size for table header text.
Anatomy
12px16px12pxBest Practices
Recommended
- ✓Use clear, descriptive header labels
- ✓Right-align numeric data
- ✓Use sticky headers for large data sets
- ✓Set a caption for accessibility
Don't
- ✗Don't create tables with too many columns
- ✗Don't use tables for layout purposes
- ✗Don't embed sort logic inside the component
- ✗Don't put overly complex content inside cells
Accessibility
Keyboard
ARIA / WCAG
- Uses semantic HTML (table/thead/tbody/th/td)
- Sort direction announced via aria-sort
- Sort icons use aria-hidden="true"
- Table description supported via caption