Design Craft

Table design is harder than it looks

Tables are some of the most information-dense UI components in any product. Most of them are designed with too little thought for how data actually behaves in them.

Tables get designed quickly because they seem straightforward: columns of data, alternating rows, a header. This speed is a trap. Tables are one of the most complex UI surfaces to get right because they have to solve alignment, density, sorting, selection, overflow, empty states, and responsive behavior simultaneously. The wrong answer on any of these creates problems that don't show up until there's real data.

The mockup with ten rows of clean, equal-length data hides all of it.

Column width is a design decision, not a default

The most common table problem is columns with arbitrary widths that don't reflect the data they contain. A "Name" column that's the same width as a "Status" column. A "Description" column that truncates at 30 characters. A "Date" column that's wide enough for a paragraph.

Column widths should be set relative to the content they're expected to contain. Short, fixed-format content (dates, status badges, IDs, currency amounts, percentages) should have fixed, narrow widths sized to their content. Variable-length content (names, descriptions, titles) should take flexible widths that expand to fill available space.

The implications extend to how the table behaves when the window is resized. If all columns are flexible, they all shrink proportionally, which means short data gets as much compression as long data, which looks wrong. A better model: fix the narrow columns at their natural width, give the flexible columns minimum widths below which they begin to truncate or wrap, and let the wide flexible columns absorb the available space.

Text alignment carries meaning

Numbers align right. Text aligns left. This isn't a convention to overrule.

Right-aligned numbers let the user compare values at a glance: the digits align at the decimal point, and scanning a column of right-aligned numbers reveals magnitude differences immediately. Left-aligned numbers force the user to mentally parse each value individually. In any financial or quantitative table, this is a meaningful UX difference.

Text columns almost always read better left-aligned, because that's how text is read: left to right, with the start of each line anchored to the left. Center-aligned text in a column looks like a design choice; it usually makes the column harder to scan.

Status columns and icon columns are the exception. A narrow column of status icons or badges can be center-aligned within its column, since the values are uniform in width and scanning isn't dependent on anchoring.

Sorting that doesn't communicate its state

Most tables support sorting, and most implementations of sorting are unclear about what's currently sorted and in which direction.

A column header that changes color on sort without any directional indicator tells the user which column is active but not whether it's ascending or descending. A directional arrow that points vaguely upward or downward needs the user to remember whether "up" means ascending or descending in this context. Conventions vary.

Clear sorting affordance: show a directional arrow on the active sort column that unambiguously communicates the direction. "Newest first" for dates, "A → Z" for text, "Low to high" for numbers: these are directions users interpret without relying on icon convention. On hover, other column headers should show a muted version of the sort arrow, indicating they're sortable.

The secondary sort behavior (what happens to ties, how multi-column sort is triggered) is rarely designed explicitly. These interactions get invented in implementation. If they matter for the use case (and in data-heavy products they often do), they should be specified.

Row selection is its own design surface

Tables that support row selection for bulk actions need a clear mental model for how selection state interacts with other table behavior: sorting, filtering, pagination, and the appearance of the selected row.

The most common failure: the selected state is visually indistinguishable from the hover state at a glance. The user selects several rows, scrolls, scrolls back, and can't tell which rows are selected and which are just hovered. Selected state needs to be distinct enough to persist visually even when the user isn't looking directly at the row.

Bulk actions, the toolbar that appears when rows are selected, should communicate the scope of the selection. "3 rows selected" is less useful than "3 of 48 rows selected" when there's a "Select all" option that might select all 48 rather than just the current page. The distinction between "select all on this page" and "select all in this dataset" is a design decision that confuses users in almost every implementation that doesn't make it explicit.

The empty table and the filtered-to-nothing table

A table with no data looks different from a table filtered to show nothing. The empty table means no data has been created or imported. The table has never had content. The filtered-to-nothing table means there is data, but none of it matches the current filter criteria.

These states need different messages. "No records yet: import a CSV to get started" is appropriate for a genuinely empty table. "No results match these filters: clear filters or adjust your search" is appropriate for a filtered table. Using a generic "No data to display" for both is confusing: the user who has just uploaded 500 records and then filtered to an empty result will interpret the message as a system error rather than a filter state.

The filtered-to-nothing state should also show the active filters, so the user understands what's being applied. A table that displays "No results" without surfacing the fact that there's a date filter set to last week is not giving the user the information they need to resolve the situation.

Responsive tables: there's no clean answer

Wide tables on small screens have no elegant solution. A 12-column table cannot be compressed to 375px without losing something. The question is what to lose.

The most common approach is horizontal scrolling. This preserves all columns but creates a surface that many users don't explore because nothing signals that it scrolls. A horizontal scroll indicator (a faded gradient at the right edge, an explicit "scroll to see more" label, a column count indicator) helps, but it's still an additional cognitive step.

The more intentional approach is to decide which columns are essential on mobile and which can be hidden or moved to a detail view. If the user's primary mobile task is to quickly scan a list and act on individual items, the mobile view might show only the identifying column and the most important status column, with a tap to expand the full row. If the user needs to compare all columns on mobile, horizontal scroll is the honest answer.

What doesn't work is applying the same layout at all widths and letting columns get so narrow they're illegible. A date column at 40px is displaying nothing useful. It's better to decide explicitly.


Tables that work well in production have usually been tested with the full range of real data: the row with a 60-character name, the row where the optional description field is blank, the filter that returns no results, the user who needs to select 200 rows at once. The mockup with 10 clean rows tests none of that. The design isn't done until those cases have answers.

Greg Sargent
Greg SargentDirector of Design Systems, Spring Health

I write about design systems, accessibility, and the way AI is changing how we build software.

Published April 23, 2026

Be the first to rate this article.