Tables still power a lot of dashboards, reports, pricing matrices, and admin screens. The problem is that classic tables were designed for wide viewports, which makes them tough to read on phones.
Hi folks, I have several basic HTML tables in a web app and I need them to work well on small screens without sacrificing accessibility. How can CSS be used to convert basic HTML tables into fully responsive layouts? I want to keep the table semantics, avoid JS if possible, and still support features like sticky headers and images inside cells. Any recommended patterns or pitfalls to avoid?
Great question. There is no single silver bullet for responsive tables, but you can combine a few CSS patterns to cover most use cases. Here are the key strategies, from least to most transformative for the layout.
Preserve semantics and design by enabling overflow on small screens. This keeps the table intact and is ideal for data-heavy pages.
<div class="table-wrap">
<table class="orders">...</table>
</div>
/* CSS */
.table-wrap {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.orders {
width: 100%;
border-collapse: collapse;
min-width: 640px; /* ensure useful scroll on narrow screens */
}
.orders th,
.orders td {
padding: 12px 8px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.orders th { white-space: nowrap; }Code language: HTML, XML (xml)
For narrow screens, turn each row into a card-like block and use a label for each value via the data-label attribute. This is very readable on phones.
<table class="orders orders--stacked">
<thead>
<tr>
<th scope="col">Order</th>
<th scope="col">Customer</th>
<th scope="col">Total</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Order">#1842</td>
<td data-label="Customer">
<img class="avatar" alt="" src="avatar.jpg"> Jane Doe
</td>
<td data-label="Total">$128.50</td>
<td data-label="Status"><span class="pill pill--ok">Shipped</span></td>
</tr>
</tbody>
</table>
/* CSS */
.orders--stacked { width: 100%; border-collapse: collapse; }
/* Visually hide the header but keep it accessible to screen readers */
.orders--stacked thead {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); border: 0;
}
.orders--stacked tr,
.orders--stacked td { display: block; width: 100%; }
.orders--stacked tr {
margin-bottom: 12px;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 8px 12px;
}
.orders--stacked td {
display: grid;
grid-template-columns: 8rem 1fr;
gap: 0.5rem;
border: 0;
padding: 10px 0;
}
.orders--stacked td::before {
content: attr(data-label);
font-weight: 600;
color: #4b5563;
}
.avatar {
width: 40px; height: 40px;
border-radius: 50%;
object-fit: cover;
}Code language: HTML, XML (xml)
Tip: If you prefer to avoid hardcoding data-label, you can generate them with a tiny script that copies header text into each cell on load.
<script>
const table = document.querySelector('.orders');
if (table) {
const headers = [...table.querySelectorAll('thead th')].map(th => th.textContent.trim());
table.querySelectorAll('tbody tr').forEach(row => {
[...row.children].forEach((cell, i) => {
cell.setAttribute('data-label', headers[i] || '');
});
});
}
</script>Code language: HTML, XML (xml)
For long lists, keep the header visible while scrolling.
.table-wrap {
max-height: 60vh; /* opt-in vertical scroll */
overflow: auto;
}
.orders th {
position: sticky;
top: 0;
background: #fff;
z-index: 1;
}Code language: CSS (css)
- Make images fluid with
max-width: 100%andheight: auto. - Use
object-fit: coverfor cropped avatars or thumbnails. - Prefer responsive image patterns and keep text readable at all sizes. If you need a refresher on how CSS sizing compares with responsive image delivery, see this guide.
- If you center media inside cells, this quick primer can help: align and center images with HTML and CSS.
- Keep semantic table markup. Use
scope="col"and clear header text. - When stacking, visually hide the header but retain it for screen readers as shown above.
- Use adequate contrast, hit areas for interactive cells, and logical tab order.
- Test with keyboard navigation and screen readers on mobile.
- Limit the number of columns on small screens. Consider grouping or collapsing optional columns.
- Lazy-load images in cells with
loading="lazy". - Audit page weight. This overview on optimizing sites is a good starting point: what is an optimized website.
Once your CSS is in place, you can keep tables fast and crisp by delivering right-sized images for each cell. Cloudinary lets you request exactly the width you need and automatically picks an efficient format. Example image in a table cell:
<img
src="https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_96/sample.jpg"
srcset="
https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_96/sample.jpg 96w,
https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_160/sample.jpg 160w,
https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_240/sample.jpg 240w
"
sizes="(max-width: 640px) 96px, 48px"
loading="lazy"
alt="Customer avatar"
class="avatar"
/>Code language: HTML, XML (xml)
That single change reduces bandwidth and improves perceived performance on mobile.
- Start with horizontal scroll for minimal changes.
- For phone-friendly readability, stack rows into card-like blocks with data-label and pseudo-elements.
- Add sticky headers and make images fluid with lazy loading.
- For extra speed, serve responsive, optimized images in cells. Cloudinary can automate format and size delivery.
- Image Upscaling and Quality Enhancement
- Convert images to WebP
- PNG to WebP Converter
- JPG to WebP Converter
Ready to speed up responsive tables with optimized media delivery? Sign up for a free Cloudinary account and start serving fast, responsive images today.