---
title: Horizontal Scroll Table
slug: horizontal-scroll-table
kind: anti-pattern
summary: Thirty columns pushed sideways off the screen because nobody decided which ones mattered — so half the data is hidden with no door and no indication it exists.
problem: >-
  Every field became a column, the table no longer fits, and the fix was a
  horizontal scrollbar. Now the identifier scrolls away from the values, nothing
  can be compared, and the half of the data that is off-screen is not reliably
  the unimportant half.
family: [scan]
principles: [progressive-disclosure, minimize-distance]
status: stable
visibility: public
related:
  - title: Dense Operational Table
    url: /patterns/dense-operational-table
    summary: The fix — a column budget, and everything else in the record.
  - title: Progressive disclosure
    url: /patterns/progressive-disclosure
    summary: Why hiding with no visible door is the specific failure here.
---

## How you get here

A horizontal scrollbar is what you reach for when the real decision — which
columns earn their place — was never made. It is technically easy and it feels
generous: nothing was removed, everything is still "available".

The reason it keeps happening is that adding a column has no visible cost to the
person requesting it, and removing one requires telling someone no.

## Why it is worse than hiding

Every disclosure pattern hides something. The difference is the **door**.

- A collapsed section has a labelled heading and a count.
- A tab has a name and a number.
- A drawer has a row you clicked.
- A horizontal scroll has **nothing**. There is no indication of how many
  columns exist, what they are, or that anything is off-screen at all.

So this is not a trade of visibility for density — it is hiding with no
affordance, which is the classic progressive-disclosure mistake and the reason
the *invisible door* failure has its own name.

## What it destroys

- **Alignment.** A table's power is that a column shares an edge, so values
  compare preattentively. Scrolled sideways, you can only see part of any column
  at a time, and comparison across the set becomes impossible.
- **Row identity.** Once the name column has scrolled away, you are reading
  numbers with no idea whose they are. Freezing the first column patches this
  and does not fix the rest.
- **Trust in the default view.** People cannot tell whether the visible columns
  are the important ones or just the first ones alphabetically. Usually they are
  the first ones added.
- **Every phone.** The pattern is unusable on a narrow screen, which is where
  much operational work now happens.

## The way out

1. Apply the **column budget**: about six or seven. Identity, status, the one or
   two comparable numbers, the owner, the driving date.
2. Ask of every other column: *would anyone ever scan this column to decide
   which row to open?* If not, it is detail, and detail belongs in the record.
3. Put the rest behind a real door — a
   [detail drawer](/patterns/master-detail-drawer), a record page, or
   [collapsible sections](/patterns/collapsible-section).
4. If different people genuinely need different columns, that is
   [saved views](/patterns/saved-views), not one table with all of them.
5. If the data genuinely is a wide grid people work across — a spreadsheet
   replacement — then build a grid, with frozen headers and a frozen identifier
   column, and know you are building a different product.

## The tell

Ask the people who use the screen to name the columns to the right of the fold.
If they cannot, those columns are not being read, and the scrollbar is
protecting a decision nobody wants to make.
