Column Priority and Hiding on Small Screens

Permalink to "Column Priority and Hiding on Small Screens"

Wide tables do not fit phones or zoomed desktop windows. One common responsive strategy is column priority: essential columns always show, less important ones hide as space shrinks. Done carelessly, it hides information with no way to get it back, or hides columns visually but leaves them in the accessibility tree, so screen reader users hear headers that sighted users cannot see. Done well, it keeps essential data visible and makes hidden values reachable in one step.

This guide belongs to responsive data tables.

Spec reference

Permalink to "Spec reference"
  • SC 1.4.10 Reflow (AA) — content usable at 320 CSS pixels wide without two-dimensional scrolling, except for content that requires it (data tables may scroll horizontally, but hiding with access is often better).
  • SC 1.3.1 (A) — hidden columns removed consistently (display: none removes from both views).
  • SC 4.1.3 (AA) — announce when the column set changes in response to user action (not on resize alone).
  • SC 1.3.2 (A) — revealed details follow their row in reading order.
Column priorities at three widths Layers: at wide widths all columns show; at medium widths priority 3 columns hide; at narrow widths priority 2 columns also hide, leaving priority 1 columns and a details disclosure per row. Column priorities at three widthsWide (1200 px)all 7 columnsMedium (800 px)priority 1 and 2: invoice, customer, amount, status, due dateNarrow (360 px)priority 1: invoice, amount, status + "Details" disclosureDetails disclosurehidden values as a labelled list under the row
Essential columns stay; others move into row details.

When to use — and when not to

Permalink to "When to use — and when not to"

Use column priority when some columns are clearly secondary for most tasks on small screens. Combine it with a column chooser so users can override defaults.

Do not hide columns users need to compare across rows; for comparison tasks, horizontal scrolling with a sticky first column is better. The misapplication: hiding columns with visibility: hidden or opacity, leaving them announced by screen readers but invisible to sighted users.

Hiding without access versus hiding with access Comparison: hiding columns with no way to see values loses information; priority hiding with a chooser and row details keeps every value one step away. Hiding without access versus hiding with access✗ Hidden, no accessValues lost on phonesUsers unaware columns existvisibility: hidden leaves headers announcedNo override✓ Hidden with accessValues in row details"3 columns hidden" with chooserdisplay: none removes consistentlyColumn chooser persists choices

Annotated code example

Permalink to "Annotated code example"
/* Priorities via classes on th and td */
@media (max-width: 900px) { .p3 { display: none; } }             /* removed from both visual and AT views (SC 1.3.1) */
@media (max-width: 600px) { .p2 { display: none; } }
@media (min-width: 601px) { .row-details-toggle { display: none; } }
<p class="hidden-cols" id="hidden-cols">3 columns hidden. <button type="button" aria-haspopup="dialog">Choose columns</button></p>
<table aria-describedby="hidden-cols">
  <thead><tr><th scope="col" class="p1">Invoice</th><th scope="col" class="p2">Customer</th>
    <th scope="col" class="p1">Amount</th><th scope="col" class="p3">Owner</th><th scope="col" class="p1">Details</th></tr></thead>
  <tbody>
    <tr>
      <th scope="row" class="p1">INV-1042</th><td class="p2">Lund &amp; Co</td><td class="p1">1,240.00</td><td class="p3">M. Ortiz</td>
      <td class="p1"><button type="button" class="row-details-toggle" aria-expanded="false" aria-controls="d-1042">Details for INV-1042</button></td>
    </tr>
    <tr id="d-1042" hidden><td colspan="5"><dl><dt>Customer</dt><dd>Lund &amp; Co</dd><dt>Owner</dt><dd>M. Ortiz</dd></dl></td></tr>
  </tbody>
</table>
// Update the hidden-column summary on resize (no announcement), announce on user choice
const update = () => { hiddenCount.textContent = `${countHidden()} columns hidden.`; };
new ResizeObserver(update).observe(table);

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Event Expected Announcement
Table at narrow width Fewer columns “Invoices, table, 3 columns”
Tab to Details button Focus “Details for INV-1042, collapsed”
Enter Details row shown “expanded”; details read next
Choose columns dialog Checkboxes per column Column names and states
Resize window Columns hide No announcement
Responsive strategies for wide tables Matrix of strategies — horizontal scroll with sticky first column, column priority with details, stacked cards, separate mobile view — against comparison across rows, completeness, reflow and complexity. Responsive strategies for wide tablesCompare acrossrowsComplete dataReflowComplexityHorizontal scroll + stickycolumnBestYesException fortablesLowColumn priority + detailsPartialVia detailsGoodMediumStacked cardsPoorYesGoodMediumSeparate mobile viewDependsDependsGoodHigh
Choose by the task on small screens.

Integration context

Permalink to "Integration context"

Horizontal scrolling alternatives are covered in keyboard scrollable table containers and sticky headers and first columns. Card layouts are in stacked card layouts that keep table semantics. The column chooser follows column visibility menus and column choosers.

Gotchas

Permalink to "Gotchas"

Zoom triggers breakpoints. At 400% zoom, desktop users hit the narrow breakpoint; make sure the experience works with a keyboard and large text, not just touch.

Details rows and table structure. A details row with colspan is a valid table row; keep it immediately after its parent row.

Persisting overrides. If users choose to show a column on small screens, remember it.

Worked example

Permalink to "Worked example"

An order management table had nine columns that overflowed on phones and at 400% zoom. The team ranked columns by task (order, status and total as priority 1), hid lower priorities with display: none at breakpoints, added “Details for {order}” disclosures showing hidden values as a description list, and a “4 columns hidden — Choose columns” summary. Zoom users no longer scrolled in two dimensions, and every value remained one button press away.

Common mistakes

Permalink to "Common mistakes"
  • visibility: hidden or opacity for hiding.
  • No way to reach hidden values.
  • Announcing column changes on every resize.
  • Hiding the identifying column.

Testing checklist

Permalink to "Testing checklist"

Design system notes

Permalink to "Design system notes"

Add priority to column definitions and let the table component generate breakpoints, the hidden-column summary and row details automatically.

Step-by-step implementation

Permalink to "Step-by-step implementation"
  1. Rank columns with real tasks. Ask what someone on a phone needs to decide what to do with a row. Usually it is the identifier, the status and one key value. Give those priority 1 and resist promoting everything.
  2. Hide with classes generated from the definition. Each column gets a priority class on both its header and its cells, so header and data hide together and the table stays rectangular.
  3. Add the details control last. The details button and the details row exist in the DOM at all widths but only display at narrow widths, so the reading order is predictable.
  4. Summarise what is hidden. Update the “3 columns hidden” text whenever the breakpoint changes, and tie it to the table with aria-describedby.
  5. Let users override. The column chooser stores per-user preferences; a user who always needs the due date on a phone should not have to open details for every row.

Edge cases

Permalink to "Edge cases"
  • Printing. Print styles should show all columns or print in landscape; hidden columns on paper cannot be revealed.
  • Copy and paste. Copying a table with hidden columns skips them; offer an export for complete data.
  • Assistive tech at zoom. Screen magnifier users at narrow effective widths benefit most from the details pattern; test with a magnifier as well as a phone.

Testing it with a screen reader

Permalink to "Testing it with a screen reader"

On an iPhone with VoiceOver, open the table and use the rotor to move by rows. Each row should contain only the visible columns plus a “Details” button, with no announcements of hidden headers. Double-tap Details and confirm that the hidden values are read next as a labelled list. On Android with TalkBack, repeat and check that the “3 columns hidden” summary is read when entering the table. On desktop, zoom to 400% and use NVDA table navigation to confirm the same structure. If a screen reader still announces a hidden column header, the column was hidden with something other than display: none.

FAQ

Permalink to "FAQ"
Does hiding columns violate reflow?

No; reflow encourages it. Hidden information must remain reachable.

Should hidden columns be announced?

State the count in a visible summary tied to the table; do not announce on resize.

Can details be in a dialog instead?

They can, but inline disclosures keep context and are simpler.

How do I choose priorities?

From user tasks on small screens: what must be seen to decide what to do with a row?

What about sorting by a hidden column?

Allow it from the sort control and mention the hidden column in the sort announcement.

Is horizontal scrolling acceptable instead?

For data tables, horizontal scrolling is an accepted exception under reflow, especially with a sticky first column. Choose per task.

Permalink to "Related"

← Back to Responsive Data Tables