HTML Tables Done Right: Accessible, Responsive, and Readable

Every junior developer I've mentored has, at some point, tried to build a table layout with CSS Grid and quietly rebuild half of a spreadsheet in the process. The moment I show them a proper <table> with <thead>, <tbody>, <th scope> and a simple overflow wrapper, the code shrinks by two thirds and screen readers finally announce it correctly. Tables get a bad rap because we misused them for page layout in the 2000s — but for actual rows and columns of data (pricing tiers, order history, admin panels — the bread and butter of the Laravel apps I ship), a semantic table is still the right tool. Here's the responsive, accessible pattern I've used on real client dashboards for years.
Semantic table structure
<table>
<caption>Quarterly sales</caption>
<thead>
<tr><th scope="col">Quarter</th><th scope="col">Revenue</th></tr>
</thead>
<tbody>
<tr><th scope="row">Q1</th><td>$120k</td></tr>
<tr><th scope="row">Q2</th><td>$155k</td></tr>
</tbody>
</table>Why scope and caption matter
Screen readers use scope to announce which header a cell belongs to. caption gives the table a name in the accessibility tree — better than a heading next to it.
Making tables responsive
Option 1: horizontal scroll
.table-wrap { overflow-x: auto; }
table { min-width: 600px; border-collapse: collapse; }Option 2: stack rows on mobile
@media (max-width: 640px) {
thead { display: none; }
tr, td { display: block; width: 100%; }
td::before { content: attr(data-label); font-weight: 600; display: block; }
}Quick accessibility checklist
- Always include <caption>
- Use <th scope="col"> and <th scope="row">
- Don't nest tables
- Keep header text short and meaningful
- Avoid colspan/rowspan if you can — they confuse screen readers
Frequently asked questions
Should I use a CSS framework's table component?
It's fine for styling, but make sure the underlying markup keeps <thead>, <th scope>, and <caption> — many frameworks drop them.
Can I sort columns without JavaScript?
Not natively. You'll need a small script or a library like list.js. Keep the unsorted state semantic.
Keep reading
External references
Enjoyed this article?
Share it with a fellow developer or explore more tutorials in our blog.
More articles