Data tables that respect the user
Sticky headers, sane defaults, and the unglamorous rules that make a table usable past 50 rows.
Data tables are where users do real work, and where most product UI falls apart. Past 50 rows, every shortcut you didn't take starts to hurt, slow sort, missing filters, broken keyboard navigation, headers that scroll out of view.
The good news is that the table problem is solved. The hard part is just not skipping any of the steps.
The non-negotiables
- 01Sticky header. When the user scrolls to row 200, the column labels need to still be visible. There is no excuse for a table without a sticky header.
- 02Sortable columns. Click the header, sort ascending. Click again, descending. Click a third time, return to default. Indicate sort direction with a chevron, not just an arrow icon.
- 03Resizable columns. Drag the border between headers to resize. Persist the user's choice in localStorage or user settings.
- 04Row hover. A subtle background change on hover, confirms which row the user is about to click.
- 05Keyboard navigation. Arrow keys to move, Enter to open, Space to select. Tables that only work with a mouse are unusable for power users.
Density modes
Three densities: comfortable (52px rows), compact (40px), tight (32px). Ship a toggle in the table toolbar. Comfortable for new users; tight for power users with 200 rows to scan. Persist the choice.
Pagination vs infinite scroll
Pagination wins for most product tables. Users know how many results there are, can jump to page 7, and can share a URL that points to a specific page.
Infinite scroll wins for feeds, Twitter, Instagram, anywhere the content is discovery-mode and the user is browsing. It loses for tables because users lose context, can't find their place, and can't share a deep link.
Virtualized rows with a scrollbar that knows the total height is the best of both, used by Linear, Notion's table view. Costs more to build, worth it past 1,000 rows.
Filters and search
Search is a top-level input, always visible. Filters are pill-shaped chips below search, visible, removable, and combinable. "Showing 47 of 1,204" should be present whenever a filter is active.
Empty states
Three different empty states matter: the truly-empty state (no data yet), the no-results state (filters returned nothing), and the loading state. Don't conflate them. "No invoices" with a CTA to create one is correct for empty; "No results for 'Acme'" with a clear-filters button is correct for no-results.
Build one
The data-table entry has the anatomy and tuned prompts for the variations above. Pair with empty states and forms, tables are almost always editing surfaces, so the row-edit flow matters as much as the read flow.
Keep reading
Dashboard layouts that actually work
Sidebar+main, top-tabs, kanban, data-grid. The four dashboard archetypes, when each one fits, and the rules that keep dense pages from feeling cramped.
Forms that feel fast (even when they're not)
Inline validation, field grouping, the multi-step pattern, and the small details that turn a 12-field form into something users actually finish.