Free shadcn/ui table component for React — the styled
primitive plus sortable, selectable, sticky-header, paginated, and expandable examples, all in plain React state (no TanStack). The examples official shadcn never shipped.
The Shadcn Table is the styled <table> — the canonical shadcn/ui table built from eight semantic parts with no dependency. Official ships the primitive and a single invoices demo; the examples below make it do real work — sort, select, paginate, and expand — all in plain React state, no TanStack required.
Table vs Data Table
This is the most common question, so up front:
Table (this page) is the static, styled markup — Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption. You bring the rows. It's plain HTML, so it's tiny and works anywhere.
Data Table is the Table driven by TanStack Table (@tanstack/react-table) — one engine that adds sorting, filtering, global search, pagination, row selection, and column visibility over large datasets.
The key insight: you rarely need TanStack. Striping, sticky headers, row selection with select-all, click-to-sort, client-side pagination, and expandable rows all work with a few lines of useState — every example below proves it. Reach for a Data Table when you have large data, server-side sorting/filtering/pagination, or faceted filters.
Striping, borders & density
The base table is borderless rows with a hover tint. Stripe it with odd:bg-muted/50 on each TableRow (the Striped rows example). For a bordered card look, wrap the table in rounded-md border; for denser rows, add [&_td]:py-1 to the Table. Right-align numeric columns with text-right tabular-nums so digits line up — see the totals row in the basic example.
Selectable rows
Row selection doesn't need a table library. Keep an array of selected ids, put a Checkbox in each row, and give the header a checkbox whose checked is true, false, or "indeterminate" based on the count. Set data-state="selected" on the chosen TableRow and the primitive highlights it for you. The Selectable rows example is the full pattern, including the indeterminate select-all.
Sortable columns
Hold a { key, dir } state, render each header as a button that toggles it, and sort a copy of the data with useMemo before mapping. An arrow icon shows the active column and direction. The Sortable headers example does client-side sort with zero dependencies.
Status badges & row actions
Tables are where status lives. Render a Badge in a cell with a variant per status (paid / pending / refunded), and add a trailing actions column of ghost icon-buttons (edit, delete). The Status & row actions example is the orders-table layout.
Sticky header & scrolling
For long tables the trick is where the scroll box lives. The Table primitive already wraps your <table> in an overflow-x-auto container, and (per the CSS spec) that container scrolls vertically too — so the height belongs on that container, not an outer div, or the sticky header has nothing to stick to:
The bg-background is essential so scrolling rows don't bleed through the header. The Sticky header example shows it in a bordered, scrollable card.
Pagination
Client-side pagination is a slice: track page and pageSize, compute pageCount, and render a rows-per-page Select with prev/next buttons. No library, no server round-trip — ideal for a few hundred rows. The Pagination example is copy-paste ready.
Expandable rows
For master/detail, track which row id is open and render a second TableRow with a colSpan detail cell beneath it when expanded. A chevron rotates to show state. The Expandable rows example implements the collapsible pattern in plain state.
Accessibility
The Table is real semantic HTML — <table>, <thead>, <tbody>, <th>, <td> — so screen readers and keyboard users get correct table semantics for free. Use TableCaption to describe the table, give icon-only action buttons an aria-label, and label selection checkboxes. Keep one logical header row so column associations stay intact.
Theming
The table reads your design tokens — --border (row dividers), --muted (hover, stripes, footer), --foreground — so it follows your theme, including dark mode, with no overrides. Style any part with a className to adjust padding, alignment, or width per column.
Frequently asked questions
Yes — it is the canonical shadcn/ui table: eight styled parts (Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption) over plain semantic HTML, with no dependency. Official ships the primitive and one invoices demo; we add striped, selectable, sortable, status, sticky-header, paginated, and expandable examples.
The Table is the static, styled markup — you bring the rows. A Data Table is the Table driven by TanStack Table (@tanstack/react-table) to add sorting, filtering, global search, pagination, row selection, and column visibility over large datasets with one engine. For moderate data you do not need it — the examples here do sorting, selection, and pagination in plain React state.
Not for most tables. Striping, sticky headers, row selection, click-to-sort, client-side pagination, and expandable rows all work with plain useState — every example here proves it. Reach for TanStack Table when you have large datasets, server-side sorting/filtering/pagination, faceted filters, or column visibility toggles, where its row models save real work.
Hold a { key, dir } sort state, render each header as a button that toggles it, and sort a copy of your data with useMemo before mapping rows. The Sortable headers example is the complete pattern — no library required.
Keep an array of selected ids. Put a [Checkbox](/components/checkbox) in each row bound to whether its id is in the array, and a header checkbox whose checked prop is true / false / "indeterminate" based on how many rows are selected. Set data-state="selected" on the selected TableRow so it highlights. The Selectable rows example shows it.
Put the height on the Table's own scroll container, not an outer div. The primitive wraps the table in an overflow-x-auto container, which (per the CSS spec) becomes the element that scrolls vertically — so set [&_[data-slot=table-container]]:max-h-[320px] on a wrapper, then make the header cells sticky with [&_th]:bg-background [&_th]:sticky [&_th]:top-0. An outer overflow-y-auto div does not work because the inner container is the real scroll box. The Sticky header example demonstrates it.
Right-align numeric columns with text-right on both the TableHead and TableCell, and add tabular-nums so digits line up in a monospace grid. Format currency with Intl.NumberFormat. You can see this in the totals row of the basic example.
Get early access to Shards Pro
Premium blocks, full-page templates and pro components — production-ready. Join the waitlist for launch access and an early-bird discount.