When choosing a React data grid, you'll encounter two fundamentally different philosophies: headless libraries like TanStack Table that provide logic without UI, and batteries-included solutions like Simple Table that come with everything out of the box.
Both TanStack Table and Simple Table are excellent, MIT-licensed options with strong TypeScript support (both have generous free tiers). But they serve different needs and come with very different development experiences. Choosing the wrong approach can cost you weeks of development time or lock you into an architecture that doesn't fit your needs. For a broader overview, check out our guide to the best React table libraries in 2025.
This guide will help you understand when to choose each approach, with real code comparisons, bundle size analysis, and practical decision criteria.
Core Philosophy: The Fundamental Difference
Headless (TanStack Table)
"We provide the brain, you provide the body."
- Provides hooks and state management logic
- Zero opinions about rendering or styles
- You write all the JSX and CSS
- Complete control over every pixel
Analogy: Like buying a car engine and building the car around it yourself. Maximum flexibility, maximum effort.
Batteries-Included (Simple Table)
"We provide a complete working table. Just configure and go."
- Provides a complete React component
- Built-in UI with sensible defaults
- Pass props, get a working table
- Customizable via props, themes, renderers
Analogy: Like buying a complete car. It works immediately, but you can still customize paint, wheels, and interior.
Code Comparison: Building the Same Table
Let's build the exact same table with both libraries: a sortable, filterable table with pagination. Here's how much code each approach requires.
TanStack Table (Headless)
// ~100 lines of code for basic features
import { useReactTable, getCoreRowModel, getSortedRowModel,
getFilteredRowModel, getPaginationRowModel, flexRender } from '@tanstack/react-table'
import { useState } from 'react'
export function TanStackTableExample({ data, columns }) {
const [sorting, setSorting] = useState([])
const [filtering, setFiltering] = useState('')
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 })
const table = useReactTable({
data,
columns,
state: { sorting, globalFilter: filtering, pagination },
onSortingChange: setSorting,
onGlobalFilterChange: setFiltering,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})
return (
<div>
{/* You build all of this UI from scratch */}
<input
value={filtering}
onChange={(e) => setFiltering(e.target.value)}
placeholder="Search..."
className="border p-2 mb-4"
/>
<table className="border-collapse border">
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className="border p-2 cursor-pointer"
>
{flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getIsSorted() ? (
header.column.getIsSorted() === 'asc' ? ' ↑' : ' ↓'
) : ''}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id} className="border p-2">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
{/* Pagination UI - you build this too */}
<div className="flex gap-2 mt-4">
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
className="px-4 py-2 border"
>
Previous
</button>
<span className="px-4 py-2">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</span>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
className="px-4 py-2 border"
>
Next
</button>
</div>
</div>
)
}
// Plus: Need to define column configuration, styling, etc.Reality check: This is a simplified example. A production-ready table with proper styling, accessibility, loading states, and virtualization would be 300-500+ lines.
Simple Table (Batteries-Included)
// ~15 lines of code for the same features
import { SimpleTable } from 'simple-table-core'
import 'simple-table-core/styles.css'
export function SimpleTableExample({ data, columns }) {
return (
<SimpleTable
rows={data}
defaultHeaders={columns}
rowIdAccessor="id"
height={400}
/>
)
}
// That's it. Everything else is built-in.
// Sorting, filtering, pagination, virtualization, accessibility - all included by default.Instant productivity: You get a production-ready table with sorting, filtering, pagination, virtualization, keyboard navigation, and accessibility built-in. Zero extra code required.
Bundle Size & Performance Reality Check
TanStack Table
- + Your table UI code (~5-10KB)
- + Virtualization library (~3-5KB)
- + Styling/CSS (~2-5KB)
- + Custom components (~5-15KB)
Simple Table
- ✓ Complete table UI
- ✓ Built-in virtualization
- ✓ Styling & themes
- ✓ All components
- ✓ Accessibility features
The Bundle Size Paradox
TanStack Table's core is lighter, but when you add the UI code you need to write, plus virtualization libraries and styling, your total bundle is often larger than Simple Table's complete package. Simple Table is optimized as a single, cohesive bundle with tree-shaking. See our comprehensive bundle size comparison for detailed analysis.
Development Speed: Time to First Table
TanStack Table Timeline
Simple Table Timeline
Cost Calculation (Senior Dev @ $100/hr)
The Tradeoff: Flexibility vs Convenience
When TanStack Table Wins
1. Ultra-Custom UI Requirements
Need a table that doesn't look like a table? Cards, timeline views, kanban boards? TanStack lets you use table logic for non-table UIs.
2. Framework Portability
TanStack works with React, Vue, Svelte, Solid. If you need the same logic across frameworks, headless makes sense.
3. Existing Design System
If you have a strict design system with existing components, building on TanStack ensures perfect visual consistency.
4. Team Expertise
Experienced team that enjoys building UI from scratch? TanStack gives them full creative control.
When Simple Table Wins
1. Speed to Market
Startup, MVP, tight deadline? Get a production-ready table in hours, not weeks. Ship features, not infrastructure.
2. Standard Table UIs
Need a table that looks like... a table? Simple Table's defaults are polished, accessible, and production-ready.
3. Small Teams / Solo Devs
Don't have time to build and maintain UI components? Simple Table lets you focus on business logic, not table internals.
4. Performance-Critical Apps
Smallest bundle + built-in virtualization = fastest load times. Perfect for mobile-first or performance-sensitive apps.
Customization: Myth vs Reality
Common Myth: "TanStack Table gives you unlimited customization. Simple Table locks you into their design."
Reality: Both are highly customizable—they just use different approaches.
TanStack Table Customization
- ✓Complete control: Write any JSX, any styles
- ✓Custom everything: Headers, cells, footers, loading states
- △But: You write 300+ lines for what Simple Table does in 10
- △Maintenance: Every customization is code you now maintain
Simple Table Customization
- ✓CSS Variables: Change colors, spacing, borders instantly
- ✓Custom Renderers: Replace any cell/header with your component
- ✓Themes: Dark mode, custom themes out of the box
- ✓Prop-based: Most customization is just changing props
The Real Question
Ask yourself: "Do I need to customize the UI structure itself, or just colors/spacing/content?"
• If structure: TanStack gives you JSX control
• If styling/content: Simple Table's renderers + CSS variables are faster
Learn more: Customizing React tables with themes | Custom renderers docs
Decision Framework: Which Should You Choose?
Choose TanStack Table When:
- You need to build non-table UIs (cards, lists, kanban) with table logic
- Your design system requires pixel-perfect custom components that don't fit standard table patterns
- You need to support multiple frameworks (React, Vue, Svelte) with shared logic
- Your team enjoys building UI and wants full creative control
- You have 1-2 weeks to build a polished table from scratch
Choose Simple Table When:
- You need a standard data table (which is 90% of use cases)
- You want to ship fast—hours, not weeks
- You're a small team or solo developer without time to build UI infrastructure
- Performance matters—you need the smallest bundle + built-in virtualization
- You want production-ready defaults with easy customization via props/themes
- You need enterprise features (row grouping, aggregation, pinning) without paying AG Grid fees
The Verdict: Start with Simple Table, Graduate to TanStack if Needed
Here's the honest advice based on working with both libraries in production:
For 90% of Projects: Start with Simple Table
Unless you have a specific reason to build UI from scratch (like truly custom layouts or multi-framework support), Simple Table will get you to production faster with less code and a smaller bundle.
Reality check: Most teams who choose TanStack initially think they need maximum flexibility, but 6 months later they've built... a standard table that looks like Simple Table's defaults. Save yourself the time.
For 10% of Projects: TanStack Table Shines
If you truly need non-standard layouts, multi-framework support, or you're building a design system from scratch, TanStack Table is the right tool. Just be honest about whether you need that level of control.
Tip: You can always migrate from Simple Table to TanStack later if you hit a wall. But you probably won't.