Table
A flexible data table component with built-in sorting, multiple visual variants, custom cell rendering, row click handling, and animated row transitions powered by Framer Motion.
Live Preview
Interact with the table component in real time. Toggle variant, hoverable, and dense mode to see changes instantly.
| Name | Role | Status | |
|---|---|---|---|
| Sarah Chen | sarah.chen@company.com | Engineering Lead | Active |
| Marcus Johnson | marcus.j@company.com | Product Designer | Active |
| Aisha Patel | aisha.p@company.com | Backend Developer | On Leave |
| David Kim | david.kim@company.com | DevOps Engineer | Active |
| Elena Rodriguez | elena.r@company.com | Frontend Developer | Remote |
1<Table2columns={columns}3data={employees}4variant="default"5hoverable={true}6dense={false}7/>Installation
Install the Aidash Components package using your preferred package manager.
$ pnpm add @aidash/componentsImport
Import the Table component and its type definitions.
import { Table } from '@/components/aidash/table';
import type { TableColumn, TableProps } from '@/components/aidash/table';Basic Usage
The simplest way to render a data table with columns and rows.
| Name | Role | Status | |
|---|---|---|---|
| Sarah Chen | sarah.chen@company.com | Engineering Lead | Active |
| Marcus Johnson | marcus.j@company.com | Product Designer | Active |
| Aisha Patel | aisha.p@company.com | Backend Developer | On Leave |
| David Kim | david.kim@company.com | DevOps Engineer | Active |
| Elena Rodriguez | elena.r@company.com | Frontend Developer | Remote |
1const columns = [2{ key: 'name', header: 'Name' },3{ key: 'email', header: 'Email' },4{ key: 'role', header: 'Role' },5{ key: 'status', header: 'Status' },6];7 8import { HugeiconsIcon } from '@hugeicons/react';9import { Tag01Icon, ArchiveIcon, ArrowDataTransferHorizontalIcon } from '@hugeicons/core-free-icons';10const data = [11{ name: 'Sarah Chen', email: 'sarah.chen@company.com', role: 'Engineering Lead', status: 'Active' },12{ name: 'Marcus Johnson', email: 'marcus.j@company.com', role: 'Product Designer', status: 'Active' },13{ name: 'Aisha Patel', email: 'aisha.p@company.com', role: 'Backend Developer', status: 'On Leave' },14{ name: 'David Kim', email: 'david.kim@company.com', role: 'DevOps Engineer', status: 'Active' },15{ name: 'Elena Rodriguez', email: 'elena.r@company.com', role: 'Frontend Developer', status: 'Remote' },16];17 18<Table columns={columns} data={data} />Variants
Three visual variants for different table aesthetics and contexts.
default
| Name | Role | Status |
|---|---|---|
| Sarah Chen | Engineering Lead | Active |
| Marcus Johnson | Product Designer | Active |
| Aisha Patel | Backend Developer | On Leave |
striped
| Name | Role | Status |
|---|---|---|
| Sarah Chen | Engineering Lead | Active |
| Marcus Johnson | Product Designer | Active |
| Aisha Patel | Backend Developer | On Leave |
bordered
| Name | Role | Status |
|---|---|---|
| Sarah Chen | Engineering Lead | Active |
| Marcus Johnson | Product Designer | Active |
| Aisha Patel | Backend Developer | On Leave |
{/* Default — clean minimal style */}
<Table columns={columns} data={data} variant="default" />
{/* Striped — alternating row backgrounds */}
<Table columns={columns} data={data} variant="striped" />
{/* Bordered — full border on all sides */}
<Table columns={columns} data={data} variant="bordered" />Dense Mode
Compact table layout with reduced padding, ideal for data-heavy views.
Normal
| Name | Role | Department |
|---|---|---|
| Sarah Chen | Engineering Lead | Engineering |
| Marcus Johnson | Product Designer | Design |
| Aisha Patel | Backend Developer | Engineering |
Dense
| Name | Role | Department |
|---|---|---|
| Sarah Chen | Engineering Lead | Engineering |
| Marcus Johnson | Product Designer | Design |
| Aisha Patel | Backend Developer | Engineering |
{/* Normal padding */}
<Table columns={columns} data={data} />
{/* Compact padding */}
<Table columns={columns} data={data} dense />Sortable Columns
Enable built-in sorting by clicking column headers. Cycles through ascending, descending, and unsorted states.
| Sarah Chen | sarah.chen@company.com | Engineering Lead | Engineering |
| Marcus Johnson | marcus.j@company.com | Product Designer | Design |
| Aisha Patel | aisha.p@company.com | Backend Developer | Engineering |
| David Kim | david.kim@company.com | DevOps Engineer | Infrastructure |
| Elena Rodriguez | elena.r@company.com | Frontend Developer | Engineering |
1const columns = [2{ key: 'name', header: 'Name', sortable: true },3{ key: 'email', header: 'Email', sortable: true },4{ key: 'role', header: 'Role', sortable: true },5{ key: 'department', header: 'Department', sortable: true },6];7 8<Table columns={columns} data={data} variant="striped" />Custom Cell Rendering
Use the render prop on columns to display avatars, badges, status indicators, and any custom content.
| Employee | Role | Department | Status |
|---|---|---|---|
SC Sarah Chen sarah.chen@company.com | Engineering Lead | Engineering | Active |
MJ Marcus Johnson marcus.j@company.com | Product Designer | Design | Active |
AP Aisha Patel aisha.p@company.com | Backend Developer | Engineering | On Leave |
DK David Kim david.kim@company.com | DevOps Engineer | Infrastructure | Active |
ER Elena Rodriguez elena.r@company.com | Frontend Developer | Engineering | Remote |
1const columns = [2{3 key: 'name',4 header: 'Employee',5 render: (value, row) => (6 <div className="flex items-center gap-3">7 <div className="w-8 h-8 rounded-full bg-brand-500/15 ...">8 {value.split(' ').map(n => n[0]).join('')}9 </div>10 <div>11 <p className="font-medium">{value}</p>12 <p className="text-xs text-muted">{row.email}</p>13 </div>14 </div>15 ),16},17{ key: 'role', header: 'Role' },18{19 key: 'status',20 header: 'Status',21 render: (value) => (22 <span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 ...">23 <span className="w-1.5 h-1.5 rounded-full ..." />24 {value}25 </span>26 ),27},28];29 30<Table columns={columns} data={employees} />Row Click Handler
Add interactive row click behavior with the onRowClick callback. Rows show a pointer cursor automatically.
| Name | Role | Department |
|---|---|---|
| Sarah Chen | Engineering Lead | Engineering |
| Marcus Johnson | Product Designer | Design |
| Aisha Patel | Backend Developer | Engineering |
| David Kim | DevOps Engineer | Infrastructure |
| Elena Rodriguez | Frontend Developer | Engineering |
1const [selected, setSelected] = useState(null);2 3<Table4columns={columns}5data={employees}6onRowClick={(row, index) => {7 setSelected(row.name);8 console.log('Clicked row', index, row);9}}10/>Custom Column Alignment
Align column content to center or right for numeric data, prices, and status fields.
| ID | Product Name | Category | ||
|---|---|---|---|---|
| PRD-001 | Wireless Headphones | Electronics | $89.99 | 142 |
| PRD-002 | Ergonomic Keyboard | Peripherals | $149.00 | 38 |
| PRD-003 | USB-C Hub | Accessories | $59.99 | Out of stock |
| PRD-004 | 4K Monitor | Displays | $499.99 | 12 |
| PRD-005 | Webcam Pro | Peripherals | $129.99 | 67 |
const columns = [
{ key: 'id', header: 'ID', width: '100px' },
{ key: 'name', header: 'Product Name' },
{ key: 'category', header: 'Category', align: 'center' },
{
key: 'price',
header: 'Price',
align: 'right',
sortable: true,
render: (value) => `$${value.toFixed(2)}`,
},
{
key: 'stock',
header: 'Stock',
align: 'right',
sortable: true,
render: (value) => (
<span className={value === 0 ? 'text-danger-500' : ''}>
{value === 0 ? 'Out of stock' : value}
</span>
),
},
];
<Table columns={columns} data={products} variant="bordered" />Row Actions
Collapse per-row actions into a dropdown menu and pin the column to the right edge, so the actions stay reachable while the rest of the table scrolls horizontally.
| Name | Role | Department | Status | Actions | |
|---|---|---|---|---|---|
| Sarah Chen | sarah.chen@company.com | Engineering Lead | Engineering | Active | |
| Marcus Johnson | marcus.j@company.com | Product Designer | Design | Active | |
| Aisha Patel | aisha.p@company.com | Backend Developer | Engineering | On Leave | |
| David Kim | david.kim@company.com | DevOps Engineer | Infrastructure | Active | |
| Elena Rodriguez | elena.r@company.com | Frontend Developer | Engineering | Remote |
Scroll the table sideways — the actions column stays pinned and picks up a divider once content passes beneath it.
1import { Table, TableRowActions } from '@/components/aidash/table';2 3const columns = [4{ key: 'name', header: 'Name' },5{ key: 'email', header: 'Email' },6{ key: 'role', header: 'Role' },7{ key: 'department', header: 'Department' },8{ key: 'status', header: 'Status' },9{10 key: 'actions',11 header: 'Actions',12 align: 'right',13 width: '72px',14 sticky: 'right', // stays put while the table scrolls sideways15 render: (_value, row) => (16 <TableRowActions17 label={`Actions for ${row.name}`}18 items={[19 { label: 'View details', icon: <HugeiconsIcon icon={EyeIcon} className="w-3.5 h-3.5" />, onClick: () => view(row) },20 { label: 'Edit', icon: <HugeiconsIcon icon={PencilEdit01Icon} className="w-3.5 h-3.5" />, onClick: () => edit(row) },21 { label: 'Duplicate', icon: <HugeiconsIcon icon={Copy01Icon} className="w-3.5 h-3.5" />, onClick: () => duplicate(row) },22 { separator: true, label: '' },23 { label: 'Delete', icon: <HugeiconsIcon icon={Delete01Icon} className="w-3.5 h-3.5" />, danger: true, onClick: () => remove(row) },24 ]}25 />26 ),27},28];29 30<Table columns={columns} data={employees} />width so it does not collapse, and TableRowActions renders its menu in a portal so the dropdown is never clipped by the table's scroll container.Empty State
When the data array is empty, the table displays a customizable message.
Default Message
| Name | Role | |
|---|---|---|
| No data available | ||
Custom Message
| Name | Role | |
|---|---|---|
| No employees found. Try adjusting your search filters. | ||
{/* Default empty message */}
<Table columns={columns} data={[]} />
{/* Custom empty message */}
<Table
columns={columns}
data={[]}
emptyMessage="No employees found. Try adjusting your search filters."
/>Accessibility
Built-in accessibility features for inclusive data table experiences.
Semantic HTML
Uses native <table>, <thead>, <tbody>, <th>, and <td> elements. Screen readers can navigate rows and columns using table navigation shortcuts.
Keyboard Navigation
Sortable column headers are focusable and can be activated with keyboard. Clickable rows respond to click events from keyboard activation on focused elements.
Visual Sort Indicators
Sortable columns display directional arrow icons that clearly indicate the current sort state (ascending, descending, or neutral), providing visual feedback alongside the data reordering.
Motion Preferences
Row animations are powered by Framer Motion which respects the prefers-reduced-motion media query. Users who prefer reduced motion will see instant transitions.
Responsive Overflow
The table wrapper uses overflow-x-auto to ensure tables remain accessible on small screens without breaking the page layout.
API Reference
Complete list of props accepted by Table and TableColumn.
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | TableColumn<T>[] | — | Array of column definitions |
| data | T[] | — | Array of row data objects |
| variant | 'default' | 'striped' | 'bordered' | 'default' | Visual style of the table |
| hoverable | boolean | true | Enable row hover highlight |
| dense | boolean | false | Compact padding for rows and cells |
| className | string | '' | Additional CSS classes for the wrapper |
| emptyMessage | string | 'No data available' | Message shown when data array is empty |
| onRowClick | (row: T, index: number) => void | — | Callback fired when a row is clicked |
| Prop | Type | Default | Description |
|---|---|---|---|
| key | string | — | Property key to read from each row object |
| header | string | — | Column header label text |
| sortable | boolean | false | Enable sorting for this column |
| width | string | — | CSS width value for the column (e.g. "200px", "30%") |
| align | 'left' | 'center' | 'right' | 'left' | Text alignment for header and cells |
| sticky | 'left' | 'right' | — | Pin the column to an edge while the table scrolls horizontally — one column per side |
| render | (value, row, index) => ReactNode | — | Custom render function for cell content |
T extends Record<string, unknown>. TypeScript will infer the type from your data array, giving you type safety in column key and render props.Related
Other components that work well alongside Table.