🧱 HTML Basics

HTML Tables Done Right: Accessible, Responsive, and Readable

By Shubham Sharma··7 min read
Responsive HTML table showing data on desktop and mobile

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.

External references

Enjoyed this article?

Share it with a fellow developer or explore more tutorials in our blog.

More articles

Related articles