There is no single solution to make any <table>
appropriately responsive. That’s what I like about this post by Davide Rizzo: it admits that, then gets on with some solutions. This is a great addition to territory we’ve been treading for a while.
Content and Comparison tables
There are many types of tables on websites where content can vary as wildly as the approaches used to make them responsive. The tables I find most frustrating are comparison tables or normal content layout tables, there are really no comprehensive CSS based solutions for making these types of tables responsive.
I set out to find a flexible and simple solution that could work as a reusable web component, regardless of the content within.

Is table markup still working for us?
Standard table markup seems to make semantic sense and does a pretty decent job of aligning cells. One of my main concerns was accessibility. Surely native table markup helps a user with a screen reader understand the order content should be read in and navigated through?
I did some tests with a simple best practice table. Using a few screen readers (Chrome Vox and VoiceOver), I attempted to navigate the markup:
- ChromeVox tells you that you are on a table, while VoiceOver also tells you how many columns and rows the table has (which is helpful).
- Neither CromeVox or VoiceOver tells you when you are on a table heading.
<thead>
,<th>
and evenscope="row|col"
don’t seem to do anything! The only way to get it to recognise a heading is to wrap it in an<h#>
tag. - The reader steps through the table via rows no matter how your content is arranged. VoiceOver at least allows you to navigate in any direction using arrow keys, but you still have no indication which order you should navigate in.
In essence, nothing in the markup tells the screen reader user if the content should be read via rows or columns. The most meaningful markup still comes from non-tabular semantic content.
Approaches for Responsive Tables
Let’s think about the different ways a table could behave responsively:
- Squash: If columns have little content they might squash horizontally with no issues on a mobile screen so not changing the layout needs to be a valid option.
- Vertical scroll: If the layout and content is exact and critical, a user could scroll to the left or right. This is trivial in CSS with an
overflow="auto"
wrapper. - Collapse by rows: Split each row into its own single column mini-table on small screens. Switching
display:table
intodisplay:block
will cause this with normal table markup. - Collapse by columns: This is where things get tricky. You can’t do this with normal table markup in pure CSS because the code order is by rows and the
<tr>
wrappers lock it in. We either have to change the markup or start manipulating with JavaScript.

Not recommended ways to build a responsive table
Through trial and experimentation, I discarded these methods:
- Generating a second narrower table via JavaScript and hide/show alternately by breakpoint.
Why? Content duplication, no better than an ‘.m’ site. Will break any unique IDs inside a table. Poor idea for Styleguide driven components. - Using normal table markup and JavaScript at a breakpoint to rearrange the table into a responsive version.
Why? Requires different markup for vertical and horizontal tables. Will break any JS initialisation of table content. Requires lots of JS event listeners and DOM manipulation. - Keeping table markup but switch to
display:flex
for vertically aligned table content.
Why? Not possible to align cells across rows with<tr>
type wrappers anddisplay: table-cell
overrides theflex-item
.
Responsive tables with flexbox
1a) For row-oriented tables…
- Order markup exactly how a mobile or screen reader should read it, use semantic headers and content.
- Abandon all concept of ‘row’ wrappers.
- Set the width of each cell as a percentage based on number of columns or rows.
Auto sizing column widths is not possible.
See the Pen Responsive Tables (By rows) by CSS-Tricks (@css-tricks) on CodePen.
1b) For column-oriented tables…
- Set the flex
order
by row to instantly create a vertical table. This must be inline otherwise we would need a unique class for every row. Fairly easy to do manually, or very easy for a CMS or JavaScript to apply.
See the Pen Responsive Tables (By columns) by CSS-Tricks (@css-tricks) on CodePen.
2) Style to help make connections
- Style cells individually in any pattern you require.
- Fix cell border duplication with negative margins.
See the Pen Responsive Tables (Cell styles) by Davide Rizzo (@davidelrizzo) on CodePen.
3) Collapse to blocks on small screens
In a small-screen media query, set everything to display: block
. That gives us responsive tables!
See the Pen Responsive Tables (Collapse) by CSS-Tricks (@css-tricks) on CodePen.
3b) Collapsing to Tabs or Accordions
- Tab and accordion markup is inside the table in a logical position
- Toggle either row or column depending on the cell
order
- Use
display: none
to toggle for both visual users and screen readers
See the Pen Responsive Tables (Tabs & Accordions) by CSS-Tricks (@css-tricks) on CodePen.
Other enhancements




Limitation: There is no way to do rowspans on a flex table.
You can use the same cell styling for other types of markup
Even standard table markup!
See the Pen Responsive Tables (Alternate markup) by CSS-Tricks (@css-tricks) on CodePen.
Fallbacks for no-flexbox
IE9 and below does not support flexbox. For older browsers, you can detect flexbox (with Modernizer) and show the mobile version, which is a good example of graceful degradation.
.no-flexbox .Rtable > .Rtable-cell {
display: block;
}
Conclusion
Overall this method offers so much flexibility that you might consider replacing all your content tables with these techniques. You can continue to add different themes and styles with ease in CSS and designers will be much more effective if they understand upfront what responsive tables are capable of.
Other Responsive Tables Resources
- Here on CSS-Tricks: Responsive Data Table Roundup
- David Bushell: CSS only Responsive Tables
- Filament Group: Tablesaw (A group of plugins for responsive tables.)
- Jason Grigsby: Picking a Responsive Tables Solution
- Here on CSS-Tricks: Responsive Data Tables
- Cody House: A (Responsive) Products Comparison Table
This post was originally published on CodePen.
Nice article. One thing worth noting though is in 1b) For column-oriented tables… you stated that you’d have to use inline styles to achieve the correct order style on each column but you sould do it using
nht-child
like this:Keep up the good work =)
That’s a nightmare to maintain. You can’t see what it does at a glance.
I can totally see what it does at a glance.
That was the tactic I tried first. The challenge is that you need a line of CSS for every possible number of rows. If you don’t know ahead of time the maximum number of rows (like in a component library) when do you stop?
I’m working on a website that has a lot of technical data and has thousands of hard coded wide html tables. The solution I used was the first non-recommended one “Generating a second narrower table via JavaScript and hide/show alternately by breakpoint”. I’m not completely satisfied with this solution but it seemed like a better option than using x scroll. It seems that every better solution would require a change to the markup.
I still think that tables should be tables — we don’t just use tables for voiceover reasons, but because the content semantically IS tabular data. Are tables a b**ch to work with when it comes to responsive? Yes, but that doesn’t mean we should avoid using them.
I was hoping to find someone else in the comments shared this view. I remain of the opinion that there is value to the semantics here. Yeah, it sucks that screen readers aren’t doing a great job of interpreting tables. That does not change the nature of tabular data.
Exactly. If a screen reader don’t do a good job reading tables, is up to them fix it.
I manage a website with a lot of tabular data that people use to do their work.
I can’t imagine changing everything to non-tabular content and removing their ability to copy and paste it into an Excel spreadsheet. This is accessibility too.
Agreed in an ideal world, I think this is a good approach for small content tables where flexibility and responsive behaviour is more important than semantics.
I tried dam hard to keep the semantics, using
<table><td>
rather than<div><div>
, you can force the table todisplay:flex
but the<td>
is inherentlydisplay:table-cell
which overrides the ‘flex-item’ behaviour of the children.display:flex-item
does not exists, perhaps an omission in the spec?Minor thoughts:
Only solution #3 is legible on my DT2 (Marshmallow, latest Chrome). The others have enough margin/padding that the words overflow
As to tables a11y:
Voiceover does have more support then mentioned above. see http://webaim.org/articles/voiceover/#tables
Also, NVDA & Jaws (significantly higher usage rates), reads TH nicely.
Still, you can go on with your approach without compromising a11y by adding ARIA roles (grid, row, columnheader, gridcell etc).
Regarding js techniques, here is my 2 cents: for small screen i pull an axis of the table content out to a select element[s]. This is kind of parallel to the tabs option….
Just found this the other day. http://dbushell.com/2016/03/04/css-only-responsive-tables/ Like it for responsive tables. Not sure how accessible it is though. By the way, in the “best practice table”, shouldn’t legend be caption instead? Also, shouldn’t tfoot come before tbody? Or are these intentional by design?
Very nice solution! Thx for this link
That is a very nice implementation of vertically scrolling responsive tables.
Thanks for the corrections on the ‘best practice tables’ pen fixed!
Legend should indeed be caption. Legend is form markup, caption is table markup. The “best practice table” example isn’t valid HTML; legend isn’t permitted at this point, it should be inside a fieldset.
tfoot can come before or after tbody. The order of these doesn’t matter.
The codepen example still shows a legend element. The actual table is fine, but legend is still shown in the
<pre><code>
example.I’m wondering if the choice of screen readers is improperly influencing the design direction. Are those two (only 2?) really indicative of the communities who will use the content? Or are they the “least functional” and therefore we need to dumb things down to reach some long tail of users? How do these design ideas work under more popular screen readers like Jaws, WIndowEyes, and NVDA
I guess you could as easily go do it the other way around – if you approach it mobile-first.
display:block
for mobile browsers/small screens and older IE by default – andflexbox
for larger screens/current IE via media query.So progressive enhancement, graceful degradation. With the added benefit that it doesn require JS or even a (maybe additional) library such as Modernizr.
Sorry, those two words went AWOL. / Underlining to highlight insert, not to convey emphasis :)
This is a great article. One thing it doesn’t really take into account is headers and footers. This works great if it’s just a table of data, but I like to have javascript place a variable on each table data cell based on the header column, then use CSS to display it on mobile. E.g.
data-label="Header Title: "
, then on mobile add a.td:before{content:" "attr(data-label);}
I feel the wrapper with overflow: auto is still the best way to go for many reasons:
The table layout is maintained … so the data keeps making sense.
Doesn’t take any extra work (avoid extra bugs)
The main downside is that you don’t always notice the horizontal scrollbar, or only when you scroll passed the bottom of a (long) table.
Good post! I think that stacking the columns and making each row a big chunk is the way to go. I wrote a jQuery plugin that does just that: https://github.com/michaelsoriano/stacked-rows
Basically it creates hidden divs below each row – that only shows up in a specific media query.
The assessment of how screenreaders deal with HTML table markup is a bit cursory – it doesn’t sound like much use was made of the table tools provided by the screenreader.
Nor should it – that’s the user’s prerogative, not the author’s.
As a sighted user, I can browse a table by row or column, as I see fit. In the case of comparison tables, I’m likely to skip around the table, checking the column and/or row headers as I go.
The table navigation tools in screenreaders provide a comparable experience. While a screenreader can certainly be left to read out the entire table row by row, there are plenty of other ways to read it. For example, I can:
navigate with arrow keys, to hear one cell at a time
reset my position to the start of the table
jump back to the start of a row, or top of a column
scan through column and row headers (e.g. jump to the start to the row, then move up and down)
ask for the row and column headers of the current cell (this makes use of
<th>
andscope
)ask for the location of the current cell (row and column number)
ignore the table entirely, and skip past it
discover other tables on the page, and skip to the one I want
The exact range of tools varies among different screenreaders (as does the manner in which they are invoked or announced), but they are broadly similar. ChromeVox provides all of the table features I’ve mentioned, though it doesn’t make much use of
thead
andtfoot
as far as I know.