Appearance
SimpleSortableTable
A lightweight, client-side table component built on TanStack Table. Perfect for simple sortable tables without the complexity of DataTable's full feature set.
Overview
SimpleSortableTable provides a streamlined table experience when you don't need server-side processing, filtering, or advanced features. It uses TanStack Table's standard column definitions, making it familiar if you've used TanStack Table before.
Key Features
- 📊 Client-Side Sorting: Multi-column sorting with visual indicators
- 📄 Pagination: Optional page-based pagination
- 🔽 Row Expansion: Expandable rows with custom content
- 👆 Row Click: Built-in row click handling
- 🎨 Density Control: Compact, normal, or spacious row heights
- ⚡ Lightweight: Minimal API, focused feature set
- 🎯 TanStack Native: Uses standard TanStack Table column definitions
When to Use
Use SimpleSortableTable when:
✅ Client-side only - All data is loaded upfront
✅ Simple sorting - No complex filtering needed
✅ Small to medium datasets - Less than 1,000 rows
✅ TanStack Table familiarity - You prefer standard ColumnDef
✅ Minimal features - Sorting and pagination are enough
✅ Quick implementation - Need a table fast
Use DataTable instead when:
❌ Server-side processing - Large datasets with backend pagination
❌ Column filters - Need text/select filters per column
❌ Advanced features - CSV export, column visibility, state persistence
❌ Global search - Need search across all columns
❌ Virtual scrolling - Need to handle 1,000+ rows efficiently
Quick Start
Basic Example
tsx
import { SimpleSortableTable } from '@/shared/ui/components/table/SimpleSortableTable'
import { type ColumnDef } from '@tanstack/react-table'
type Task = {
id: string
title: string
priority: number
status: string
}
const columns: ColumnDef<Task>[] = [
{
accessorKey: 'title',
header: 'Task Title',
},
{
accessorKey: 'priority',
header: 'Priority',
enableSorting: true,
},
{
accessorKey: 'status',
header: 'Status',
},
]
export function TaskList({ tasks }: { tasks: Task[] }) {
return (
<SimpleSortableTable
columns={columns}
data={tasks}
initialSort={[{ id: 'priority', desc: true }]}
/>
)
}Features
1. Sorting
Enable sorting on any column:
tsx
const columns: ColumnDef<Task>[] = [
{
accessorKey: 'title',
header: 'Title',
enableSorting: true, // Enable sorting
},
{
accessorKey: 'priority',
header: 'Priority',
enableSorting: true,
},
]
<SimpleSortableTable
columns={columns}
data={tasks}
initialSort={[
{ id: 'priority', desc: true }, // Sort by priority descending
{ id: 'title', desc: false }, // Then by title ascending
]}
/>Multi-column sorting: Click multiple column headers while holding Shift.
2. Row Click
Handle row clicks for navigation or selection:
tsx
import { useNavigate } from 'react-router-dom'
function ClickableTable() {
const navigate = useNavigate()
return (
<SimpleSortableTable
columns={columns}
data={tasks}
onRowClick={(task) => navigate(`/tasks/${task.id}`)}
selectedRowId={currentTaskId} // Highlight selected row
/>
)
}3. Row Expansion
Add expandable rows for additional details:
tsx
function ExpandableTable() {
return (
<SimpleSortableTable
columns={columns}
data={tasks}
expandable
renderExpandedRow={(task, toggleExpand) => (
<div className="p-4 bg-muted/50">
<h4 className="font-semibold mb-2">Task Details</h4>
<p className="text-sm text-muted-foreground">{task.description}</p>
<div className="mt-4 flex gap-2">
<Button size="sm" onClick={() => editTask(task)}>
Edit
</Button>
<Button size="sm" variant="outline" onClick={toggleExpand}>
Close
</Button>
</div>
</div>
)}
multiExpand={false} // Only one row expanded at a time
/>
)
}Controlled expansion:
tsx
function ControlledExpansion() {
const [expandedIds, setExpandedIds] = useState<string[]>([])
return (
<>
<Button onClick={() => setExpandedIds([])}>
Collapse All
</Button>
<SimpleSortableTable
columns={columns}
data={tasks}
expandable
renderExpandedRow={(task) => <TaskDetails task={task} />}
expandedRowIds={expandedIds}
onExpandedChange={setExpandedIds}
/>
</>
)
}4. Pagination
Add pagination for long lists:
tsx
<SimpleSortableTable
columns={columns}
data={tasks}
enablePagination
defaultPageSize={20}
pageSizeOptions={[10, 20, 50, 100]}
/>5. Density Control
Adjust row spacing:
tsx
// Compact - minimal padding
<SimpleSortableTable density="compact" {...props} />
// Normal - balanced (default)
<SimpleSortableTable density="normal" {...props} />
// Spacious - generous padding
<SimpleSortableTable density="spacious" {...props} />6. Custom Cell Rendering
Use TanStack Table's cell render function:
tsx
const columns: ColumnDef<Task>[] = [
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => (
<Badge variant={row.original.status === 'done' ? 'default' : 'secondary'}>
{row.original.status}
</Badge>
),
},
{
accessorKey: 'assignee',
header: 'Assignee',
cell: ({ row }) => (
<div className="flex items-center gap-2">
<Avatar src={row.original.assignee.avatar} size="sm" />
<span>{row.original.assignee.name}</span>
</div>
),
},
]Column Definition
SimpleSortableTable uses standard TanStack Table ColumnDef:
tsx
import { type ColumnDef } from '@tanstack/react-table'
const columns: ColumnDef<YourType>[] = [
{
id: 'unique-id', // Optional: unique identifier
accessorKey: 'fieldName', // Field to access from data
accessorFn: (row) => row.computed, // Or computed value
header: 'Column Header', // String or component
cell: ({ row }) => row.original.field, // Custom cell render
enableSorting: true, // Enable sorting
enableHiding: true, // Enable hiding
size: 200, // Column width
minSize: 100, // Min width
maxSize: 400, // Max width
},
]Examples
Task Management Table
tsx
import { SimpleSortableTable } from '@/shared/ui/components/table/SimpleSortableTable'
import { Badge } from '@/shadcn/components/ui/badge'
import { type ColumnDef } from '@tanstack/react-table'
type Task = {
id: string
title: string
status: 'todo' | 'in-progress' | 'done'
priority: 'low' | 'medium' | 'high'
assignee: string
dueDate: string
}
const columns: ColumnDef<Task>[] = [
{
accessorKey: 'title',
header: 'Task',
enableSorting: true,
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const statusColors = {
'todo': 'secondary',
'in-progress': 'default',
'done': 'outline',
}
return (
<Badge variant={statusColors[row.original.status]}>
{row.original.status}
</Badge>
)
},
},
{
accessorKey: 'priority',
header: 'Priority',
enableSorting: true,
cell: ({ row }) => (
<span className={cn(
'font-medium',
row.original.priority === 'high' && 'text-destructive',
row.original.priority === 'medium' && 'text-warning',
)}>
{row.original.priority}
</span>
),
},
{
accessorKey: 'assignee',
header: 'Assignee',
},
{
accessorKey: 'dueDate',
header: 'Due Date',
enableSorting: true,
},
]
function TaskTable({ tasks }: { tasks: Task[] }) {
const navigate = useNavigate()
return (
<SimpleSortableTable
columns={columns}
data={tasks}
initialSort={[{ id: 'dueDate', desc: false }]}
onRowClick={(task) => navigate(`/tasks/${task.id}`)}
density="normal"
enablePagination
defaultPageSize={25}
/>
)
}Product Inventory Table
tsx
type Product = {
id: string
sku: string
name: string
category: string
stock: number
price: number
}
const columns: ColumnDef<Product>[] = [
{
accessorKey: 'sku',
header: 'SKU',
size: 120,
},
{
accessorKey: 'name',
header: 'Product Name',
enableSorting: true,
},
{
accessorKey: 'category',
header: 'Category',
},
{
accessorKey: 'stock',
header: 'Stock',
enableSorting: true,
cell: ({ row }) => (
<span className={cn(
row.original.stock < 10 && 'text-destructive font-semibold'
)}>
{row.original.stock}
</span>
),
},
{
accessorKey: 'price',
header: 'Price',
enableSorting: true,
cell: ({ row }) => `$${row.original.price.toFixed(2)}`,
},
]
function InventoryTable({ products }: { products: Product[] }) {
return (
<SimpleSortableTable
columns={columns}
data={products}
expandable
renderExpandedRow={(product) => (
<div className="p-4 space-y-2">
<p><strong>Description:</strong> {product.description}</p>
<p><strong>Supplier:</strong> {product.supplier}</p>
<p><strong>Last Restocked:</strong> {product.lastRestocked}</p>
</div>
)}
/>
)
}Best Practices
✅ Do's
- Use for client-side data - All data loaded upfront
- Keep datasets reasonable - Under 1,000 rows for best performance
- Provide row IDs - Use
getRowIdfor stable row identity - Enable sorting - On columns users will sort by
- Use TanStack patterns - Follow TanStack Table conventions
❌ Don'ts
- Don't use for large datasets - Use DataTable with virtual scrolling instead
- Don't expect server-side features - No server callbacks
- Don't use for complex filtering - Use DataTable with column filters
- Don't forget accessibility - Provide proper labels and ARIA attributes
Comparison with DataTable
| Feature | SimpleSortableTable | DataTable |
|---|---|---|
| Sorting | ✅ Client-side | ✅ Client + Server |
| Filtering | ❌ No | ✅ Text + Select filters |
| Pagination | ✅ Client-side | ✅ Client + Server |
| Global Search | ❌ No | ✅ Yes |
| Column Visibility | ❌ No | ✅ Yes |
| Column Resizing | ❌ No | ✅ Yes |
| CSV Export | ❌ No | ✅ Yes |
| State Persistence | ❌ No | ✅ Yes |
| Virtual Scrolling | ❌ No | ✅ Yes |
| Row Selection | ❌ No | ✅ Yes |
| Row Expansion | ✅ Yes | ❌ No |
| Row Click | ✅ Yes | Via rowActions |
| Density Control | ✅ Yes | ✅ Yes |
| Mobile Responsive | ❌ No | ✅ Card view |
| API Complexity | Simple | Advanced |
| Bundle Size | Smaller | Larger |
Choose SimpleSortableTable for simple, client-side tables.
Choose DataTable for advanced features and server-side processing.
API Reference
For detailed prop documentation, see:
Related Components
- DataTable - Full-featured data table
- TablePagination - Standalone pagination
- EditableTableCell - Inline editing
Migration
From DataTable to SimpleSortableTable
If you're using DataTable but only need basic features:
tsx
// Before (DataTable)
import { DataTable, type DataTableColumn } from '@/shared/ui/components/table/DataTable'
const columns: DataTableColumn<Task>[] = [
{ id: 'title', header: 'Title', cell: (row) => row.title, sortable: true },
]
<DataTable columns={columns} data={tasks} />
// After (SimpleSortableTable)
import { SimpleSortableTable } from '@/shared/ui/components/table/SimpleSortableTable'
import { type ColumnDef } from '@tanstack/react-table'
const columns: ColumnDef<Task>[] = [
{ accessorKey: 'title', header: 'Title', enableSorting: true },
]
<SimpleSortableTable columns={columns} data={tasks} />Benefits: Simpler API, smaller bundle, standard TanStack Table patterns.