A data table is the component where design and engineering disagree most, and both are usually right. Design wants air, rounded corners and a card per row. Engineering wants ten thousand rows to scroll at sixty frames a second. The resolution is not a compromise: it is noticing that a table is for scanning, and everything that helps scanning is what design should be optimising.
319 components on 21st carry the table tags. Below is what the shape is for, then the four features that separate a table from a grid of text.
The tables
Interactive Logs Table by Moumen Soliman is the dense operational shape. Server Management Table, Leads Data Table and Financial Markets Table by Isaiah are three domain versions of the same skeleton, and Resizable Table by the same author adds column sizing.
Project Data Table by Ravi Katiyar and RuixenContributorsTable by Ruixen UI are the product-shaped ones.




The four features that make it a table
Sorting that says which column and which direction. A header that sorts needs aria-sort and a visible indicator, and the sort has to survive pagination, because sorting page one of forty is not sorting.
Filtering with visible state. The active filters belong above the table as removable chips, not hidden behind a panel. A reader who cannot see why the table has eleven rows will assume the data is missing.
Selection that survives a page change. Selecting six rows, moving to page two and losing them is the single most common data-table bug. Keep the selection keyed by id, and show the count.
Column control. Which columns show, and in what order. Past about eight columns this stops being a nicety, because different roles read different subsets of the same table.
Numbers, alignment and density
The part that is genuinely a design decision and has one correct answer.
Right-align numbers, left-align text, and use font-variant-numeric: tabular-nums so digits line up in a column. That combination is what makes a column of figures scannable at a glance; proportional digits turn it into a ragged edge that has to be read one row at a time.
Density is a real setting, not a preference. An operational table wants 32 to 36 pixel rows and a listing wants 48 to 56. Offering both as a toggle is more useful than picking one.
Where the performance goes
Three thresholds, and knowing which one you are at saves a rewrite.
Under about 100 rows. Anything works. Sort and filter on the client.
A few hundred to a few thousand. The DOM node count becomes the problem before the data does. Virtualise the rows, which keeps the node count flat, at the cost of breaking find-in-page for anything offscreen.
Past that. The server has to do the work. Sorting, filtering and pagination move to the query, the client asks for one page at a time, and the URL carries the state so a sorted, filtered view is a link someone can send.
The mistake that costs the most is doing server-side pagination with client-side sorting, which sorts the visible page and looks correct until someone checks.
The markup, and the mobile question
A table is a <table>. <th scope="col">, a caption, and real rows: that is what lets a screen reader announce "column 3 of 7, Status" as the reader moves. A grid of divs with display: grid cannot do that, and it is the most common accessibility failure in this category.
On a phone, a wide table has three honest options and one bad one. Horizontal scroll inside its own container, with the first column sticky. A card per row, which works when there are few columns and reads badly with many. Or a chosen subset of columns with the rest behind a detail view. The bad option is shrinking the text until it fits.
Where else to look
The honest list, because the answer is not always us:
| Source | Best for | Trade-off |
|---|---|---|
| TanStack Table | Sorting, filtering, grouping and selection as headless logic | You write every pixel of the table |
| shadcn/ui Data Table | TanStack Table already wired to the primitives | A starting point rather than a finished table |
| TanStack Virtual | Thousands of rows at a stable frame rate | Breaks find-in-page |
| A grid library | Editing, freezing, pivoting, spreadsheet behaviour | Large, and usually commercially licensed |
| 21st | Finished operational and product tables to start from | Quality varies by author, so preview before you take it |
Taking one
Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:
That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue. The 21st MCP covers the same ground without leaving the editor: ask Claude, Cursor or Codex for a data table, get the previews inline, and let the agent write the file.
Frequently asked
- What makes a data table usable?
- Four things: sorting that announces its column and direction and survives pagination, filters visible as removable chips above the table, selection keyed by id so it survives a page change, and column control, which stops being a nicety past about eight columns because different roles read different subsets.
- When does a table need virtualisation?
- Somewhere between a few hundred and a few thousand rows, where the DOM node count becomes the bottleneck before the data does. Past that, move sorting, filtering and pagination to the server and keep the state in the URL. The costly mistake is server-side pagination with client-side sorting, which sorts only the visible page.
- Should a data table use table markup?
- Yes. th with scope, a caption and real rows are what let a screen reader announce column three of seven, Status, as the reader moves. A grid of divs cannot do that, and it is the most common accessibility failure in this category.
- How do you handle a wide table on mobile?
- Three honest options: horizontal scroll in its own container with the first column sticky, a card per row when there are few columns, or a chosen subset with the rest behind a detail view. Shrinking the text until it fits is the option that is not honest.


