Table
- Component overview: Data table built around a data-driven
columns + dataSourceAPI. Layout, alignment, empty states, and merged cells should all be expressed through column and data configuration at the component layer. - Interaction: Row hover highlights, column alignment, and empty-state placeholder. Sorting, selection, and pagination are handled at the business layer.
- Implementation note:
ui/tableretains the structural skeleton (scroll wrapper, border, hover); the design layer adds a data-driven rendering path on top. - Figma reference
Basic Usage
Pass columns and dataSource for automatic table rendering — the simplest way to use the component.
Result
Loading...
Live Editor
render( <Table columns={[ { title: 'Name', dataIndex: 'name', width: 160 }, { title: 'Email', dataIndex: 'email' }, { title: 'Role', dataIndex: 'role', width: 120 }, ]} dataSource={[ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Owner' }, { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Editor' }, { id: 3, name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer' }, ]} rowKey="id" />, )
Custom Render
Use the render function in column definitions to customize cell content.
Result
Loading...
Live Editor
render( <Table columns={[ { title: 'Name', dataIndex: 'name', width: 160 }, { title: 'Duration', dataIndex: 'duration', width: 100 }, { title: 'Date created', dataIndex: 'createdAt', width: 180, render: (value) => new Date(String(value)).toLocaleDateString(), }, { title: '', key: 'action', width: 60, align: 'center', render: () => ( <button style={{ cursor: 'pointer', border: 'none', background: 'transparent' }}> ··· </button> ), }, ]} dataSource={[ { id: 1, name: 'Meeting Notes', duration: '40m 39s', createdAt: '2026-03-10T17:47:03' }, { id: 2, name: 'Project Review', duration: '25m 12s', createdAt: '2026-03-08T09:30:00' }, ]} rowKey="id" />, )
Column Alignment
Columns support left (default), center, and right alignment.
Result
Loading...
Live Editor
render( <Table columns={[ { title: 'Item', dataIndex: 'item', align: 'left' }, { title: 'Quantity', dataIndex: 'qty', align: 'center', width: 100 }, { title: 'Price', dataIndex: 'price', align: 'right', width: 120 }, ]} dataSource={[ { id: 1, item: 'Plaud NotePin', qty: 2, price: '$169.00' }, { id: 2, item: 'Plaud Note', qty: 1, price: '$159.00' }, ]} rowKey="id" />, )
Nested Data Index
Use an array for dataIndex to access nested object fields.
Result
Loading...
Live Editor
render( <Table columns={[ { title: 'Name', dataIndex: 'name' }, { title: 'City', dataIndex: ['address', 'city'] }, { title: 'Country', dataIndex: ['address', 'country'] }, ]} dataSource={[ { id: 1, name: 'Alice', address: { city: 'Beijing', country: 'China' } }, { id: 2, name: 'Bob', address: { city: 'Tokyo', country: 'Japan' } }, ]} rowKey="id" />, )
Header Tip
Set tip on a column to render the Figma-aligned help icon in the header and show explanatory content on hover.
Result
Loading...
Live Editor
render( <Table columns={[ { title: 'Name', dataIndex: 'name', tip: 'Primary display name shown in the table.' }, { title: 'Email', dataIndex: 'email' }, { title: 'Status', dataIndex: 'status', tip: 'Status is synced from the latest review result.', width: 140, }, ]} dataSource={[ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', status: 'Active' }, { id: 2, name: 'Bob Smith', email: 'bob@example.com', status: 'Pending' }, ]} rowKey="id" />, )
Empty State
Set emptyText to show a placeholder when dataSource is empty.
Result
Loading...
Live Editor
render( <Table columns={[ { title: 'Name', dataIndex: 'name' }, { title: 'Email', dataIndex: 'email' }, ]} dataSource={[]} emptyText="No data available" />, )
Token Table
| Element | Property | Token / Value |
|---|---|---|
| Header | Text color | Labels/Tertiary#757575 |
| Header | Height | 40px (h-10) |
| Header | Bottom border | [&_tr]:border-b |
| Row | Hover background | Backgrounds/Tertiary (50%)rgba(0,0,0,0.03) |
| Row | Selected background | Backgrounds/Tertiaryrgba(0,0,0,0.05) |
| Cell | Padding | 8px (p-2) |
| Footer | Background | Backgrounds/Tertiary (50%)rgba(0,0,0,0.03) |
Props
Table (data-driven)
| Prop | Type | Default | Description |
|---|---|---|---|
columns | TableColumnDef<T>[] | - | Column definitions |
dataSource | T[] | - | Data array |
rowKey | string | ((record: T) => Key) | 'key' or 'id' | Unique row identifier |
emptyText | ReactNode | - | Content shown when dataSource is empty |
className | string | - | Custom style class |
TableColumnDef
| Field | Type | Default | Description |
|---|---|---|---|
key | string | - | Unique column key — falls back to dataIndex |
dataIndex | string | string[] | - | Data field path — supports nested paths like ['address', 'city'] |
title | ReactNode | - | Column header content |
tip | ReactNode | - | Tooltip content rendered from the header help icon |
sortable | boolean | auto | Whether the column renders sort affordance |
render | (value, record, index) => ReactNode | - | Custom cell renderer |
width | number | string | - | Column width |
align | 'left' | 'center' | 'right' | 'left' | Text alignment |
className | string | - | Column className applied to both th and td |