Data tables for people who live in them

There is a user who opens your product at 8:55am and lives inside one table until 5pm. She has opinions about your column order. She has a personal relationship with the export button. She does not want your table to be beautiful; she wants it to be fast, dense and honest — and she will forgive almost anything except a row that lies to her.

This is the user marketing-site thinking can't design for, and it's why table UX deserves its own discipline. We learned it the hard way rebuilding the Northwind Ledger dashboard, where accountants had been exporting to Excel not because they loved Excel, but because our table was a slideshow pretending to be a tool. These are the patterns that turned it into something they now live in.

Density is a preference, not a design decision

Consumer-design instincts say whitespace is kindness. For heavy tables, whitespace is latency — every extra pixel of row height is rows that fit on screen, and rows on screen are the entire game. A finance analyst scanning 40 rows doesn't read; she pattern-matches, and every scroll resets the pattern.

So we ship three density modes and make the middle one genuinely tight:

  • Compact: 32px rows, 12.5px tabular numerals, single-line cells, truncation with tooltips. The default for ops screens.
  • Comfortable: 44px rows for mixed audiences and demos. Yes, partly demos — honesty about who reviews your screenshots matters.
  • Spacious: nearly nobody wants this. Ship it anyway; accessibility and low-vision contexts thank you later.

The toggle lives in the table toolbar, persists per-table (an transactions table and a customers table are different jobs), and — critically — changes only density. A mode switch that also reshuffles columns is a small betrayal.

Sticky is a negotiation

Sticky headers are uncontroversial until you implement them on a table that also has a sticky page header, a filter bar and a bulk-actions bar. Four stacked sticky layers and 14 rows visible is not a win.

Our rules, in order of importance:

  1. Column headers stick. Always. A table you can scroll past the labels of is a guessing game.
  2. The first column sticks only if it's the identity column. Row checkboxes, invoice numbers, names — the thing that answers "which row am I on?" at horizontal scroll. Sticking a low-meaning column (an avatar, a status dot) steals width for decoration.
  3. Pagination or total-count bar sticks to the bottom of the container, not the viewport. Otherwise shorter tables float it in mid-air and taller ones hide it.
  4. Bulk-action bars appear on selection and replace the toolbar, not stack on it. One sticky layer at a time.

And test it all at 100% browser zoom on a 13-inch laptop, which is where your users actually live. The accessibility-in-the-design-file rule applies doubly here: sticky scroll-shadows, keyboard focus trapping on horizontal scroll, and focus-return after row actions are design decisions with engineering consequences.

Column management: the spreadsheet fidget

Every heavy-table product eventually grows a column menu, and most of them are junk drawers. The pattern that works:

  • Show/hide with live preview, not a save button. Users tune columns the way they tune a chair — continuously, by feel.
  • Drag to reorder, with the identity column pinned in place. Let everything else move.
  • "Reset to default" always visible. Permission to experiment. The moment rearranging feels permanent, nobody rearranges.
  • Per-user persistence, exportable to the team. The head of ops will standardise her team's columns by hand if you let her; make that a "share this view" feature instead of a Sunday afternoon.

Saved views — filter + sort + column state bundles with names — are the single highest-retention feature we've shipped on table-heavy products. Northwind's "Month-end close" view, maintained by their own finance lead, did more for stickiness than any onboarding flow could. Views are how a table becomes theirs.

Inline editing: integrity before speed

Inline editing in a table is a promise: this works like a spreadsheet. Spreadsheets earn that promise with forty years of trust. You have to earn it per interaction.

Non-negotiables:

  • Optimistic edits display their status. Changed cell shows a subtle marker ("saving…" then a brief confirmation, then clean). Silent cell writes are how data gets distrusted, and distrust is how your product becomes a staging area for Excel again.
  • Undo for every inline edit, held for at least ten seconds, at row level. Bulk edits get a review sheet before commit — 400 rows changed in one hotkey is a support incident, not a feature, unless the user saw it coming.
  • Validation happens at commit, not keystroke. Red-underline-while-typing in a table cell is harassment. Validate on blur or Enter, and write the failure well — the error-message discipline applies in miniature: what happened, what's preserved, what to do.
  • Escape always cancels. Enter commits, Tab commits-and-moves, Escape abandons. Spreadsheet muscle memory is real and you will not retrain it.

Keyboard speed runs

Give a power user fifteen minutes and she'll ask if the table is "keyboardable". This is a compliment available to you. The core set:

  • Arrow keys move a cell cursor when a cell is focused (and a visible focus ring shows where — see every rule we've ever written about focus states).
  • Enter edits, or opens the row, depending on context — pick one per table and document it.
  • / focuses table search; Esc clears and returns focus to where it was.
  • Shift+arrows or Shift+click extends selection; one keystroke exposes the bulk bar.
  • Cmd/Ctrl+K (or a visible "jump to") for row-level navigation in tables with thousands of rows.

You don't need the full spreadsheet keymap. You need the six that recite like a prayer, shipped with a ?-shortcut cheat sheet, and — this matters — you need keyboard selection to be the same object as mouse selection so bulk actions behave identically. Two selection models is a bug farm.

Export honesty

The export button is where tables confess everything. Principles:

  • Export what you see. Filtered view exports the filtered rows, in the visible column set, with a summary line in the export ("Exported 214 of 8,311 rows, filtered: status=overdue"). Silent full-dumps generate spreadsheet archaeology and billing disputes.
  • Format for the destination, not the screen. Export raw values: real dates, unrounded numbers, currency as number + separate column for the unit. "1.2k" in a CSV is vandalism.
  • Large exports go async. Over ~10k rows, generate in the background and email a link. A synchronous 90-second spinner is an error message with ambition.

Export honestly and the exodus stops: users who trust the table stop hoarding their data outside it.

Where to start

If your table needs everything on this list, sequence it: keyboard navigation and density first (felt daily), saved views second (retention), inline-editing integrity third (trust), export honesty fourth (cost). Design the keyboard and selection models before the visual polish — they are the chassis. Our product design practice treats table-heavy tools as their own genre for exactly this reason, and the dashboard-dense project work is the reference implementation.

Key takeaways

  • Density is the user's call: three modes, tight middle, per-table persistence.
  • Sticky layers are a budget. Headers always stick; identity columns stick; everything else argues for it.
  • Saved views — named filter + sort + column bundles — are the retention feature hiding inside every table.
  • Inline edits must visibly save, undo cleanly, and validate at commit. Silence destroys trust.
  • Ship six keyboard shortcuts, not sixty, and make keyboard selection the same object as mouse selection.
  • Export what's on screen, with raw values and honest metadata. Big exports go async.

FAQ

Should we use virtualised scrolling or pagination? Virtualised (infinite) scrolling for scanning and exploration; numbered pagination when position matters — auditing, "we were on page 12", print reconciliation. Finance and compliance contexts almost always want pagination with stable totals. Never change row ordering under a paginated user without telling them.

How many columns is too many? The table can hold dozens; the default view shouldn't show more than 8–10. Past that, horizontal scrolling breaks row comprehension and sticky-first columns become mandatory. The column menu exists precisely so the default can stay honest.

Cards on mobile, table on desktop? Usually the right move: reflow each row into a card with the identity field prominent and secondary fields collapsed behind an expander. What you must not do is force horizontal scroll on a phone — nobody triages invoices with two thumbs.

Do we need a design system before building heavy tables? You need three things pinned down before the first complex table: tabular numerals in the type scale, a row-state model (hover/selected/editing/dirty), and a selection model shared by mouse and keyboard. The rest can grow. Those three retrofit terribly.


Keep reading