What tables are for (and what they ruined)
One question decides it
Open any admin dashboard a Rails app has ever rendered: a list of orders, users, invoices, deploys, and you'll find the same shape: rows of records, columns of attributes. Before writing a single tag, ask the question that decides whether <table> is the right tool:
Would this content make sense pasted into a spreadsheet, with column headers that describe every cell below them?
If yes, it's tabular data and <table> is the correct, semantic choice. If you just want things side by side visually, it isn't a table. CSS, a few courses ahead on this roadmap, is the tool for that. That distinction sounds obvious, but the history of the web says otherwise.
The anatomy, at its smallest
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Blue T-Shirt</td>
<td>$25.00</td>
</tr>
</table>
Three tags carry the whole structure:
| Tag | Stands for | Job |
|---|---|---|
<table> |
table | The container announcing "grid of data" |
<tr> |
table row | One horizontal record |
<td> |
table data | One cell holding a value |
<th> |
table header | One cell describing the cells in its column or row |
Notice what is missing: there is no "column" tag you write cells into. Columns are implied: the first cell of every row lines up into column one, the second into column two, and so on. Rows are explicit; columns emerge.
th is not "bold td"
Browsers render <th> bold and centered by default, which tempts beginners to use it as a styling shortcut, or to skip it and bold a <td> instead. Both miss the point, and it's the same point every course in this series has hammered: the tag encodes meaning, not looks.
A <th> tells the browser (and, far more importantly, a screen reader) "this cell labels others." When a screen reader user lands on a cell deep inside a large table, the software announces the row and column headers associated with it: "Price, Blue T-Shirt, $25.00." Strip the <th> elements out and that user hears "$25.00" with no idea what the number means or which record it belongs to. Lesson 5 goes deep on this; for now, the rule is simple: every real data table has headers, and headers are <th>.
What the browser builds from this
The DOM Basics course showed that markup becomes a live tree with roles attached. Tables get a whole family of them:
table → role: table
tr → role: row
th → role: columnheader (or rowheader)
td → role: cell
That role structure is the reason assistive technology can navigate a table cell by cell, column by column, the way your eye scans a spreadsheet. It's also the first thing to check in the DevTools Accessibility panel when a table misbehaves: a habit this course will build lesson by lesson.
The dark ages: layout tables
For roughly a decade (the late 1990s into the 2000s) <table> was the primary page layout tool of the entire web. Sites nested tables inside tables inside tables to build sidebars, navigation bars, and multi-column pages, because CSS layout barely existed. A typical page skeleton looked like this:
<!-- How pages were built circa 2001. Never do this. -->
<table width="100%">
<tr>
<td width="200">navigation links...</td>
<td>all the actual content...</td>
</tr>
</table>
It worked visually and it was a disaster semantically: screen readers announced navigation menus as data grids, the markup was unmaintainable, and the meaning of every page was buried under structural lies. Modern CSS (Flexbox and Grid, both on this roadmap) ended that era completely. Today the rule has no exceptions worth learning: tables are for data, CSS is for layout. If you inherit an old codebase and find a layout table, you've found archaeology, not a technique.
One curious survivor: HTML email. Email clients' CSS support is stuck decades behind browsers, so marketing emails still use table-based layout internally. That's a specialized corner with its own tooling: worth knowing it exists so a peek at an email template doesn't confuse you, not something this course teaches.
Why this matters for Rails
The @products.each loop is the beating heart of most Rails index views, and its natural rendering target is a table row:
<table>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
<% @products.each do |product| %>
<tr>
<td><%= product.name %></td>
<td><%= product.price %></td>
</tr>
<% end %>
</table>
One record per <tr>, one attribute per <td>: the database table and the HTML table mirror each other almost line for line. Rails scaffolds stopped generating tables in Rails 7: an index view now renders one <div> per record through a partial. Admin screens, reports and dashboards still want tables, though, so table fluency pays off quickly in real Rails work.
How this course is organized
- What tables are for, and the layout-table cautionary tale (you are here)
- Rows, cells, and headers:
tr,td,th, and what browsers silently fix - Giving a table structure:
thead,tbody,tfoot, andcaption - Cells that span:
colspan,rowspan, and the grid math that keeps them honest - Tables screen readers can navigate:
scopeand friends - Tables that don't fit: handling overflow on small screens
- Putting it together: a production-shaped Rails index table
By the end, the tables you write will be the kind a reviewer approves without comment: correct for sighted users scanning columns, correct for screen reader users navigating cells, and correct for the browser building its accessibility tree. The place to start is with what happens when you write a table slightly wrong: because the browser has opinions about that, and Lesson 2 shows you them in DevTools.
Keep going with lesson 2
Rows, cells, and what the browser fixes in silence
Covers rows, cells, column and row headers, empty cells, and the tbody and text fixes browsers apply.
A free account also gives you the quizzes, your progress and notes, and the discussion.