What makes good data table design?
Good data table design lets people find a row, compare values and act on what they find, quickly and without errors. Tables are where most work in a web app actually happens: reviewing accounts, approving requests, triaging tickets. A table that's slow to scan or awkward to filter costs users time every single day.
Within a dashboard, tables usually sit below the KPIs and charts and answer "which ones?" Our dashboard design guide covers that wider layout. This article goes deep on the table itself, and on something most table guides skip: how the design gets built in React, so the spec your designer writes is the one your developer ships.
Principle | In practice |
|---|---|
Scannable | Clear alignment, restrained borders, readable row height |
Comparable | Right-aligned numbers, tabular figures, consistent units |
Findable | Visible search, filters and sorting |
Actionable | Row actions, row selection and bulk actions |
Honest about state | Distinct empty, zero-results, loading and error states |
How should you lay out columns and rows in table UI design?
Lay out columns in order of importance from left to right, starting with the column that identifies the row, such as a name or ID. In table UI design, alignment does most of the work: left-align text, right-align numbers so decimal places line up, and align each header with its column's content.
- First column identifies the row. Usually a name with a secondary line, and it stays visible when the table scrolls sideways.
- Right-align numbers and use tabular figures. Put units in the header ("Revenue, USD") rather than repeating them in every cell.
- Keep headers sticky on long tables so people never lose track of which column is which.
- Use restraint with lines. Horizontal row dividers or subtle zebra striping help; full grid borders add noise.
- Truncate with care. Truncate long text with an ellipsis and show the full value on hover or in the detail view, never in a way that hides the distinguishing part.
- Let people choose columns. Column visibility, and on wide tables reordering, helps different roles see what they need.
What are table UX best practices for sorting and filtering?
The core table UX best practices for sorting and filtering are to make the current sort and active filters always visible, keep them easy to change or clear, and pick sensible defaults. Users should never wonder why a row is missing or why the order looks wrong.
- Sorting: click a header to sort, show an arrow on the sorted column, and default to the order that matches the task, such as most recent first or most urgent first.
- Filtering: show common filters inline above the table, and put rarer ones in a filter panel. Show each active filter as a removable chip with a "Clear all" option.
- Search: one search box that searches the columns people expect, with a note on what's searched if it isn't obvious.
- Persistence: remember a user's sort, filters and columns between visits, and reflect them in the URL so a filtered view can be shared.
- Counts: show the result count ("48 of 1,240 accounts") so people know how much filters have narrowed the list.
Filter design gets complicated fast once you add facets, saved views and combined conditions. Our guide to search and filter UX covers faceted search in detail.
How do you design bulk actions UX?
Design bulk actions UX around row selection: a checkbox per row, a select-all in the header, and an action bar that appears only once something is selected. The action bar shows how many rows are selected and the actions available for them, which keeps the default view clean and makes the selection state obvious.
Select
Checkboxes on each row and in the header. Shift-click to select a range is a welcome extra for power users.
Clarify the scope
When select-all is used on a paginated table, ask whether to select this page or all matching rows, and show the count.
Show the action bar
A bar with "12 selected", the available actions and a clear way to deselect.
Confirm destructive actions
Deleting or changing many records needs a confirmation that names the count and the effect.
Report the result
Say what happened, including partial failures ("10 updated, 2 couldn't be changed"), and offer undo where possible.
Bulk actions matter even more when some actions are taken by AI, such as suggested updates waiting for approval. Our guide to human-in-the-loop UX covers review queues and undo for those cases.
Which density modes should a data grid offer?
A data grid should usually offer two or three density modes, such as comfortable, default and compact, because different users and tasks need different row heights. Someone reviewing ten accounts a day wants room to read; someone reconciling hundreds of transactions wants as many rows on screen as possible.
Density | Best for | Trade-off |
|---|---|---|
Comfortable | Occasional users, rich rows with avatars or two lines | Fewer rows per screen |
Default | Most users and most tasks | A balance of scanning and reading |
Compact | Power users, finance and ops tables, large screens | Harder to click on touch, denser to read |
Density should change spacing and row height, not font size. Shrinking text to fit more rows makes everything harder to read. Store the density choice per user, and make sure compact rows still have large enough click targets for checkboxes and row actions. Our product design glossary explains density and the related terms designers use.
What's the difference between an empty state and zero results?
An empty state means there's no data yet, while zero results means data exists but the current search or filters exclude all of it. They look identical if you only show an empty table, but they need opposite responses: the first helps someone create data, the second helps them loosen their filters.
State | Cause | Message | Primary action |
|---|---|---|---|
Empty (first use) | Nothing created or imported yet | Explain what will appear here | Create, import or connect |
Zero results | Filters or search too narrow | "No accounts match these filters" | Clear filters, edit search |
Cleared | User finished all items, e.g. an inbox | A short confirmation | None, or go elsewhere |
No permission | User can't see these records | Say access is restricted | Request access |
Error | The query failed | Plain explanation | Retry |
How do you spec a data table so it builds in React with TanStack Table?
Spec a data table in the same structure your developers will use to build it. With TanStack Table, a popular headless library for React, that means column definitions, sorting, filtering, pagination, row selection and column visibility. TanStack Table handles the table logic and state but no visuals, so your design system supplies the markup and styles.
- Column definitions: for each column, the header label, the data field, alignment, cell format (currency, date, badge), whether it sorts, whether it filters and its default visibility.
- Sorting and filtering: say whether they run in the browser or on the server. Large tables usually need server-side sorting, filtering and pagination, which the library supports through manual modes.
- Row selection: which rows can be selected, how select-all behaves across pages, and which bulk actions are available.
- Pagination or virtualization: paginate when people need to reference positions; virtualize long scrolling lists so only visible rows render.
- States: loading skeleton rows, empty, zero-results, error and partial failure, as specified above.
- Responsive behavior: which columns hide first, whether the first column pins, and when the table becomes a list on small screens.
Hypothetical example: a column spec for an invoices table
For example, suppose a billing product has an invoices table. A buildable spec might read:
Customer: text, left-aligned, sortable, searchable, pinned on horizontal scroll, always visible. Amount: currency, right-aligned, tabular figures, sortable, filter by range. Status: badge with label (Paid, Due, Overdue), filter by multi-select, default filter excludes Paid. Due date: date, sortable, default sort ascending. Actions: menu with View, Send reminder, Mark as paid; hidden from read-only roles.
Bulk actions: Send reminders and Export, available when one or more rows are selected. Server-side sorting and pagination, 50 rows per page, sort and filters stored in the URL. That's a spec a developer can map directly to column definitions without guessing.
When one team owns both design and code, the spec and the build come from the same decisions, which is the point of working this way. For tables inside larger internal tools, see our guide to admin panel design, and run your table against the SaaS dashboard checklist.
