Responsive Tables — Complete Guide
In this tutorial, you will learn about Responsive Tables. We cover key concepts, practical examples, and best practices to help you master this topic.
Responsive tables use techniques like horizontal scrolling, column toggling, row collapsing, stacked card layouts, and data reflow to present tabular data clearly on small screens.
What You'll Learn
- Why tables break on small screens
- Horizontal scrolling tables
- Stacked/reflow table pattern
- Column toggling and priority columns
- Responsive table with CSS Grid
- Accessible responsive tables
Why It Matters
- Data tables are essential for comparing information
- Fixed-width tables overflow on mobile
- Users need to see column headers alongside data
- Accessibility must be maintained in responsive transforms
Real-World Use
- A pricing comparison table stacks on mobile
- A financial data table scrolls horizontally on small screens
- A schedule grid collapses to a list view
- A product specification table uses priority columns
flowchart LR
A[Data Table] --> B{Screen Width}
B --> C[Wide Screen]
B --> D[Narrow Screen]
C --> E[Standard Table]
D --> F[Stacked Cards]
D --> G[Horizontal Scroll]
D --> H[Priority Columns]
Responsive Table Patterns
Tables are inherently wide. On small screens, they require transformation to remain usable.
Horizontal Scroll
The simplest approach: let the table scroll horizontally. This preserves the table structure but requires scrolling.
Stacked/Reflow
Each row becomes a card or group with labels on the left and values on the right. This is most readable but transforms the table nature.
Priority Columns
Show only the most important columns on mobile, with a toggle to see all columns. Requires JavaScript to manage visibility.
Code Example: Horizontal Scroll Table
<div class="table-wrapper">
<table class="responsive-table">
<caption>Quarterly Sales Report 2026</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Q1 Revenue</th>
<th scope="col">Q2 Revenue</th>
<th scope="col">Q3 Revenue</th>
<th scope="col">Q4 Revenue</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Widget A</th>
<td data-label="Q1 Revenue">$45,000</td>
<td data-label="Q2 Revenue">$52,000</td>
<td data-label="Q3 Revenue">$58,000</td>
<td data-label="Q4 Revenue">$63,000</td>
<td data-label="Total">$218,000</td>
</tr>
<tr>
<th scope="row">Widget B</th>
<td data-label="Q1 Revenue">$32,000</td>
<td data-label="Q2 Revenue">$38,000</td>
<td data-label="Q3 Revenue">$41,000</td>
<td data-label="Q4 Revenue">$47,000</td>
<td data-label="Total">$158,000</td>
</tr>
</tbody>
</table>
</div>
<style>
.table-wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin: 1rem 0;
border: 1px solid #ddd;
border-radius: 8px;
}
.responsive-table {
width: 100%;
border-collapse: collapse;
min-width: 600px; /* Forces scroll below this width */
}
.responsive-table th,
.responsive-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #eee;
white-space: nowrap;
}
.responsive-table th {
background: #f5f5f5;
font-weight: 600;
position: sticky;
top: 0;
z-index: 1;
}
/* Shadow indicators for scrollable container */
.table-wrapper {
background:
linear-gradient(90deg, white 30%, transparent),
linear-gradient(-90deg, white 30%, transparent);
background-size: 40px 100%, 40px 100%;
background-position: 0 0, 100% 0;
background-repeat: no-repeat;
background-attachment: local, local;
}
</style>
Expected output: On desktop, a standard table. On mobile, the table scrolls horizontally with sticky headers. Shadow indicators on the sides hint at scrollable content. The overflow-x: auto and min-width create the horizontal scroll.
Code Example: Stacked Card Table
/* Stacked table using flexbox on mobile */
@media (max-width: 768px) {
.stacked-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.stacked-table tbody tr {
display: block;
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
padding: 0.5rem;
}
.stacked-table td {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
.stacked-table td:last-child {
border-bottom: none;
}
/* Show data-label before each cell */
.stacked-table td::before {
content: attr(data-label);
font-weight: 600;
color: #666;
margin-right: 1rem;
}
}
Expected output: On mobile, each row becomes a card. Column headers are hidden from visual but remain accessible via data-label attributes shown before each cell value. The label-value pair makes it easy to understand data without scrolling.
Code Example: Priority Column Table
<table class="priority-table">
<caption>Product Comparison</caption>
<thead>
<tr>
<th scope="col">Feature</th>
<th scope="col" class="priority-high">Basic</th>
<th scope="col" class="priority-high">Pro</th>
<th scope="col" class="priority-medium">Enterprise</th>
<th scope="col" class="priority-low">Ultimate</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Price</th>
<td class="priority-high">$19/mo</td>
<td class="priority-high">$49/mo</td>
<td class="priority-medium">$99/mo</td>
<td class="priority-low">$199/mo</td>
</tr>
<tr>
<th scope="row">Users</th>
<td class="priority-high">5</td>
<td class="priority-high">25</td>
<td class="priority-medium">100</td>
<td class="priority-low">Unlimited</td>
</tr>
<tr>
<th scope="row">Storage</th>
<td class="priority-high">10 GB</td>
<td class="priority-high">100 GB</td>
<td class="priority-medium">1 TB</td>
<td class="priority-low">10 TB</td>
</tr>
<tr>
<th scope="row">Support</th>
<td class="priority-high">Email</td>
<td class="priority-high">Email + Chat</td>
<td class="priority-medium">24/7 Phone</td>
<td class="priority-low">Dedicated</td>
</tr>
</tbody>
</table>
<style>
/* Hide columns based on priority at different widths */
@media (max-width: 1024px) {
.priority-low { display: none; }
}
@media (max-width: 768px) {
.priority-medium { display: none; }
}
/* Toggle button to show all columns */
.table-toggle {
margin: 0.5rem 0;
padding: 0.5rem 1rem;
min-height: 44px;
cursor: pointer;
}
.table-toggle[aria-pressed="true"] + table .priority-medium,
.table-toggle[aria-pressed="true"] + table .priority-low {
display: table-cell;
}
</style>
<button class="table-toggle"
aria-pressed="false"
onclick="toggleColumns(this)">
Show all columns
</button>
<script>
function toggleColumns(button) {
const pressed = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', !pressed);
button.textContent = pressed ? 'Show all columns' : 'Show fewer columns';
}
</script>
Expected output: On large screens, all 4 plan columns are visible. On tablets, the lowest priority column (Ultimate) is hidden. On phones, both medium and low priority columns are hidden. A toggle button lets users show all columns.
Common Mistakes
- Not specifying a min-width on tables — Without min-width, the browser shrinks columns to unreadable widths before triggering scroll.
- Hiding thead on mobile without accessible fallback — Screen readers need the table structure. Use
data-labelattributes on cells to maintain context. - Making each cell too small to tap — Touch targets in stacked tables should be at least 44px.
- Not using sticky headers — Scrolling through a long table without visible headers is confusing.
- Forgetting to handle rowspan/colspan — Responsive transforms break with merged cells. Plan for this.
- Losing sort functionality on mobile — If tables are sortable, the sort controls must work on touch.
- Performance issues with large tables — Hundreds of responsive rows can cause performance issues. Consider pagination or virtual scrolling.
Practice Questions
- What CSS property enables horizontal scrolling on a table container?
overflow-x: autoon the wrapper element, plusmin-widthon the table. - How do you maintain column header context in a stacked table layout? Use
data-labelattributes on each cell and display them with::beforepseudo-elements in the mobile CSS. - What is the priority column technique? Assigning importance levels (high, medium, low) to columns and hiding lower priority columns at smaller breakpoints.
- Why should you use
-webkit-overflow-scrolling: touch? It provides smooth, momentum-based scrolling on iOS devices. - Challenge: Build a responsive product comparison table with 5 products and 8 features. Implement all three responsive patterns on one page with tabs to switch: horizontal scroll, stacked cards, and priority columns with toggle. Test accessibility with a screen reader and ensure touch targets are adequate.
FAQ
Mini Project
Build a comprehensive data dashboard with three table types. Table 1: Financial data with 8 columns (horizontal scroll on mobile, shadow indicators, sticky headers). Table 2: Team member directory with 5 columns (stacked card on mobile with data-label attributes, searchable). Table 3: Product comparison with 6 columns (priority columns with show/hide toggle). All tables must have proper caption, scope, and accessible structure. Include a toggle to switch between table and card view. Test with keyboard navigation and screen reader at multiple viewports.
What's Next
Continue with Lesson 13: Responsive Forms to learn how to create forms that work well on any device.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro