Skip to content

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 getRowId for 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

FeatureSimpleSortableTableDataTable
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✅ YesVia rowActions
Density Control✅ Yes✅ Yes
Mobile Responsive❌ No✅ Card view
API ComplexitySimpleAdvanced
Bundle SizeSmallerLarger

Choose SimpleSortableTable for simple, client-side tables.
Choose DataTable for advanced features and server-side processing.


API Reference

For detailed prop documentation, see:



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.


See Also