
Responsive HTML Tables: How to Make Tables That Work on Mobile
September 29, 2026
Why Responsive HTML Tables Break on Mobile
Tables are the best way to present structured data, such as pricing plans, product specs, schedules or comparison charts. But open a typical HTML table on a phone and you’ll often see the same problems: text squeezed into narrow columns, content cut off at the edge of the screen, or the whole page scrolling sideways.
The reason is simple. A table is a two-dimensional grid, and its width is the sum of all its columns. A table with six columns and some longer text easily needs 800 pixels or more, while most phones only have around 375 pixels to work with. Fixed widths (width: 900px), long words that can’t wrap and simply having too many columns all make it worse.
In this guide, you’ll learn four proven techniques for building a responsive table with HTML and CSS, how to do the same with Tailwind CSS, and how to skip the CSS work entirely with Table Generator.
The Semantic Base: Start With Proper HTML
Every responsive table technique starts with clean, semantic markup. Before you write a single line of CSS, make sure your table looks like this:
<table>
<caption>Houseplant care guide</caption>
<thead>
<tr>
<th scope="col">Plant</th>
<th scope="col">Height</th>
<th scope="col">Watering</th>
<th scope="col">Temperature</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Snake Plant</th>
<td>60 cm</td>
<td>Every 14 days</td>
<td>15-25 °C</td>
</tr>
<tr>
<th scope="row">Peace Lily</th>
<td>45 cm</td>
<td>Every 10 days</td>
<td>18-24 °C</td>
</tr>
</tbody>
</table>
Here’s what each element does:
<caption>gives the table a title. Screen readers announce it before the table content, and search engines use it to understand what the table is about.<thead>and<tbody>separate the header from the data. This is required for some techniques below, like sticky headers.<th scope="col">marks a column header,<th scope="row">marks a row header. Screen readers usescopeto read out the matching header for each cell, so users know that “60 cm” is the height of the Snake Plant.
Semantic tables are also good for SEO. Search engines can extract data from well-structured tables and sometimes show it directly in search results. A table made of <div> elements, or worse, an image of a table, gives them nothing to work with.
Technique 1: Horizontal Scroll Wrapper
The simplest and most robust way to make a table responsive is to let it scroll horizontally inside a container:
<div class="table-wrapper">
<table>
...
</table>
</div>
.table-wrapper {
max-width: 100%;
overflow-x: auto;
}
.table-wrapper table {
min-width: 600px;
border-collapse: collapse;
}
The wrapper never gets wider than its parent, and if the table needs more room, only the wrapper scrolls, not the whole page. The min-width on the table keeps the columns from being squeezed into unreadable slivers before scrolling kicks in.
To make the scroll area accessible for keyboard users, add tabindex="0", a role and a label so the container can be focused and scrolled with the arrow keys:
<div class="table-wrapper" tabindex="0" role="region" aria-label="Houseplant care guide">
Pros:
- Works with any table, no matter how many columns.
- Keeps the table structure intact, so comparing rows and columns is still easy.
- Just two lines of CSS.
Cons:
- Users may not notice that the table can be scrolled. A subtle shadow on the edge or a “scroll for more” hint helps.
- Users lose context when they scroll right and the first column disappears. Technique 3 solves this.
For most tables, this is the right default. It’s also the approach Table Generator uses for its embeddable tables.
Technique 2: Stacked “Card” Layout
For tables where each row describes one item (a product, a person, an order), you can turn every row into a card on small screens. Each cell becomes its own line, with the column name shown next to the value.
First, add a data-label attribute with the column name to each cell:
<tr>
<td data-label="Plant">Snake Plant</td>
<td data-label="Height">60 cm</td>
<td data-label="Watering">Every 14 days</td>
<td data-label="Temperature">15-25 °C</td>
</tr>
Then use a media query to switch the table elements to block layout and show the labels with a ::before pseudo-element:
@media (max-width: 640px) {
.card-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.card-table tr {
display: block;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
}
.card-table td {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #f3f4f6;
}
.card-table td::before {
content: attr(data-label);
font-weight: 600;
}
}
The header row is hidden visually but kept available for screen readers. Each row becomes a bordered card, and each cell shows its label on the left and its value on the right.
Pros:
- No horizontal scrolling at all.
- Works great for lists of items that users read one at a time.
Cons:
- Comparing values across rows becomes harder.
- You have to duplicate the column names in every cell (
data-label), which is easy to forget when the table changes. - Changing
displayon table elements can remove their table semantics in some browsers. If accessibility is critical, add explicit ARIA roles (role="table",role="row",role="cell") or test with a screen reader.
Technique 3: Sticky Header and First Column
When users scroll through a large table, they quickly lose track of which column or row they’re looking at. position: sticky keeps the header row and the first column in place:
.table-wrapper {
max-width: 100%;
max-height: 400px;
overflow: auto;
}
.table-wrapper table {
border-collapse: separate;
border-spacing: 0;
}
.table-wrapper thead th {
position: sticky;
top: 0;
z-index: 1;
background: #f3f4f6;
}
.table-wrapper tbody th {
position: sticky;
left: 0;
background: #ffffff;
}
.table-wrapper thead th:first-child {
left: 0;
z-index: 2;
}
A few details are important here:
- The sticky cells need a background color, otherwise the scrolling content shows through them.
- The top-left cell is sticky in both directions and needs a higher
z-indexso it stays on top. border-collapse: collapsecan cause borders to scroll away from sticky cells in some browsers. Usingborder-collapse: separatewithborder-spacing: 0avoids this.- A sticky header only works if the scrolling container is the closest scrolling ancestor. Setting a
max-heighton the wrapper makes the header stick inside the table. Without it, the header sticks to the top of the page (as long as no parent element hasoverflowset).
This technique pairs perfectly with the horizontal scroll wrapper from technique 1 and is ideal for wide data tables, comparison tables and schedules.
Technique 4: Hiding Lower-Priority Columns
Not every column is equally important. On a small screen, you can hide columns that are nice to have but not essential:
<tr>
<th scope="col">Plant</th>
<th scope="col">Watering</th>
<th scope="col" class="optional">Height</th>
<th scope="col" class="optional">Temperature</th>
</tr>
@media (max-width: 640px) {
.optional {
display: none;
}
}
Remember to add the class to both the header cell and the matching data cells in every row.
Pros:
- The remaining columns get enough space to be readable.
- Very little CSS.
Cons:
- Mobile users can’t access the hidden data at all. Only use this for data that’s truly optional, or offer a way to see the full table (for example, a link to a detail page or a “show all columns” button).
You can combine this with the scroll wrapper: hide the least important columns and let the rest scroll if they still don’t fit.
Responsive Tables With Tailwind CSS
If you’re using Tailwind CSS, you can build all of the patterns above with utility classes. Here’s a responsive table with a scroll wrapper, a sticky header, a sticky first column and a hidden column on small screens:
<div class="max-h-96 max-w-full overflow-auto rounded-lg border border-gray-200" tabindex="0" role="region" aria-label="Houseplant care guide">
<table class="min-w-[600px] border-separate border-spacing-0 text-left text-sm">
<caption class="sr-only">Houseplant care guide</caption>
<thead>
<tr>
<th scope="col" class="sticky top-0 left-0 z-20 bg-gray-100 px-4 py-3 font-semibold text-gray-900">Plant</th>
<th scope="col" class="sticky top-0 z-10 bg-gray-100 px-4 py-3 font-semibold text-gray-900">Watering</th>
<th scope="col" class="sticky top-0 z-10 hidden bg-gray-100 px-4 py-3 font-semibold text-gray-900 sm:table-cell">Height</th>
<th scope="col" class="sticky top-0 z-10 bg-gray-100 px-4 py-3 font-semibold text-gray-900">Temperature</th>
</tr>
</thead>
<tbody class="text-gray-600">
<tr class="even:bg-gray-50">
<th scope="row" class="sticky left-0 border-t border-gray-200 bg-white px-4 py-3 font-medium text-gray-900">Snake Plant</th>
<td class="border-t border-gray-200 px-4 py-3">Every 14 days</td>
<td class="hidden border-t border-gray-200 px-4 py-3 sm:table-cell">60 cm</td>
<td class="border-t border-gray-200 px-4 py-3">15-25 °C</td>
</tr>
<!-- more rows -->
</tbody>
</table>
</div>
The key utilities are:
overflow-autoandmax-w-fullfor the scroll wrapper (technique 1).sticky top-0on header cells andsticky left-0on the first column (technique 3).hidden sm:table-cellto hide a column on small screens (technique 4). Notetable-cellinstead ofblock, so the column displays correctly when it’s visible again.
For the card layout (technique 2), Tailwind’s responsive variants work too: use block sm:table-row on rows and block sm:table-cell on cells, and show the labels with before:content-[attr(data-label)] sm:before:content-none.
By the way: Table Generator can export any table as HTML with Tailwind CSS classes already applied, so you don’t have to write them by hand.
Styling Basics for Readable Tables
A responsive table also needs to be easy to read. A few simple rules go a long way:
table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
line-height: 1.5;
}
th,
td {
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e7eb;
text-align: left;
vertical-align: top;
}
thead th {
background: #f3f4f6;
font-weight: 600;
}
tbody tr:nth-child(even) {
background: #f9fafb;
}
td.number {
text-align: right;
font-variant-numeric: tabular-nums;
}
- Zebra stripes (
nth-child(even)) help the eye follow a row across the table, especially in wide tables. - Horizontal borders separate rows without making the table look cluttered. Vertical borders are rarely needed.
- Generous padding makes cells easier to read and tap on touch screens.
- Readable font sizes: don’t go below 14px (0.875rem) on mobile. Shrinking the font is not a substitute for a responsive layout.
- Right-aligned numbers with
tabular-numsmake it easy to compare values in a column.
Common Mistakes to Avoid
Using tables for layout. Tables are for tabular data, not for positioning content on a page. Use CSS Grid or Flexbox for layouts. Layout tables confuse screen readers and are nearly impossible to make responsive.
Missing header cells. Using <td> with bold text instead of <th> might look the same, but screen readers and search engines can’t tell which cells are headers. Always use <th> with a scope attribute.
Publishing tables as images. A screenshot of a table can’t be read by screen readers (unless it has a detailed alt text), can’t be indexed by search engines, can’t be copied and doesn’t scale on small screens. If you need an image for social media or a presentation, use it in addition to a real HTML table on your website, not instead of it.
Setting fixed widths. A width="900" attribute or a fixed width in pixels guarantees overflow on mobile. Use max-width: 100% on the container and let the content define the column widths.
Forgetting to test on real devices. Browser developer tools are great, but always check your table on an actual phone. Touch scrolling, font rendering and screen sizes can behave differently.
Skip the CSS: Generate a Responsive Table
If you don’t want to write and maintain all this CSS yourself, Table Generator does it for you. Create your table in the visual editor, style it with borders, stripes, colors and font sizes, and export it in the format you need:
- Embeddable HTML: paste a snippet into any website, including WordPress, Webflow and other site builders. The table is responsive out of the box and its styles won’t clash with your website. Learn more about why Web Components make sense for table embedding.
- HTML with Tailwind CSS classes: clean, semantic markup with utility classes, ready to drop into your project.
- Plain HTML: a semantic table without styling, for when you want to use your own CSS.
Here’s a live example created with Table Generator and embedded into this page. Try resizing your browser window or opening this page on your phone to see how it adapts:
| Technique | Best For | Keeps Table Structure | Horizontal Scrolling | Effort |
|---|---|---|---|---|
| Horizontal scroll wrapper | Any table, especially wide data tables | Yes | Yes | Low |
| Stacked card layout | Lists of items, like products or contacts | No | No | Medium |
| Sticky header and first column | Large tables and comparison charts | Yes | Yes | Medium |
| Hiding columns | Tables with optional, secondary data | Yes | Reduced | Low |
| Table Generator embed | Websites and site builders without custom CSS | Yes | Only when needed | None |
New to Table Generator? Read our introduction to Table Generator for an overview of all features and export options.
Frequently Asked Questions
How do I make an HTML table responsive?
The easiest way is to wrap the table in a <div> with overflow-x: auto and max-width: 100%. The table can then scroll horizontally on small screens without breaking the page layout. For more advanced layouts, you can turn rows into cards, make the header sticky or hide less important columns with media queries.
Can I make a responsive table without CSS?
Not with plain HTML alone. HTML defines the structure of a table, and CSS controls how it behaves on different screen sizes. If you don’t want to write CSS, you can use a tool like Table Generator to create a table that’s responsive out of the box and embed it on your website.
What is the best way to display a table on mobile?
It depends on the data. For data that users need to compare across rows and columns, a horizontal scroll wrapper with a sticky first column works best. For lists of items that users read one at a time, a stacked card layout is often easier to read on a phone.
Should I use a table or CSS Grid?
Use a <table> for tabular data, where values are related across both rows and columns. Use CSS Grid for page layouts and card grids. Tables give screen readers and search engines important information about the structure of your data that a grid of <div> elements can’t provide.
How do I make a responsive table with Tailwind CSS?
Wrap the table in a <div> with overflow-auto max-w-full, use sticky top-0 for a sticky header and hidden sm:table-cell to hide columns on small screens. Or create your table in Table Generator and export it as HTML with Tailwind CSS classes.
Create Your Responsive Table Now
Building responsive tables by hand takes time, and every new table means repeating the same work. With Table Generator, you can create a beautiful, responsive table in minutes and export it as embeddable HTML, Tailwind CSS, plain HTML, PNG, PDF and more. Give it a try, it’s free!
Related Articles
Markdown Table Syntax: The Complete Guide (with Examples and a Generator)
Learn Markdown table syntax with copyable examples: pipes, alignment, formatting inside cells, GitHub vs. GitLab vs. Notion, limitations, and a free Markdown table generator.
Read more about Markdown Table Syntax: The Complete Guide (with Examples and a Generator)How to Add a Table in WordPress (3 Easy Ways, No Plugin Needed)
Learn how to add a table in WordPress without a plugin: use the built-in Table block, weigh up table plugins, or embed a styled table that keeps its look in any theme.
Read more about How to Add a Table in WordPress (3 Easy Ways, No Plugin Needed)Why Web Components for Table Embedding Simply Make Sense
Discover the power of Web Components with Table Generator for beautiful, consistent tables on any platform. Easily embed tables with responsive design and enhanced search engine visibility.
Read more about Why Web Components for Table Embedding Simply Make Sense