HTML Table Rows and Cells
If a table is a grid, then rows and cells are its building blocks. You already know the basic recipe from Table Basics: a row holds cells, and cells hold content. This lesson goes one level deeper. You will see how the table grid really works, what you can put inside a cell, and the small CSS touches (like right-aligning numbers) that make a table far easier to read.
What You Will Learnβ
- How the table grid is built from rows and cells
- What a row (
<tr>) can and cannot contain - What can go inside a cell, and what to avoid
- How to align text and numbers, and set vertical alignment
- How to control wrapping and width
- How to handle empty cells and mixed
<th>and<td>rows - Common mistakes with rows and cells
The Grid Modelβ
Every table is a grid. Each cell sits at the crossing of one row and one column, just like a spreadsheet's A1, B2, and so on.
column 1 column 2 column 3
row 1 ββββββββββββ¬βββββββββββ¬βββββββββββ
β 1 Β· 1 β 1 Β· 2 β 1 Β· 3 β
row 2 ββββββββββββΌβββββββββββΌβββββββββββ€
β 2 Β· 1 β 2 Β· 2 β 2 Β· 3 β
row 3 ββββββββββββΌβββββββββββΌβββββββββββ€
β 3 Β· 1 β 3 Β· 2 β 3 Β· 3 β
ββββββββββββ΄βββββββββββ΄βββββββββββ
You build it in the code row by row, left to right:
<table>
<tr>
<td>1 Β· 1</td> <td>1 Β· 2</td> <td>1 Β· 3</td>
</tr>
<tr>
<td>2 Β· 1</td> <td>2 Β· 2</td> <td>2 Β· 3</td>
</tr>
<tr>
<td>3 Β· 1</td> <td>3 Β· 2</td> <td>3 Β· 3</td>
</tr>
</table>
The browser keeps track of each cell's position. Cell "2 Β· 3" is the third cell in the second row, and it appears in column 3. Later lessons on headings, scope, and accessibility all build on this simple idea: a cell's meaning comes from its row and its column.
Table Rows: <tr>β
The <tr> element is one horizontal line of cells.
What a Row May Containβ
Allowed directly inside <tr> | Not allowed |
|---|---|
<td> (data cells) | Text on its own |
<th> (header cells) | <p>, <div>, <span> |
Script-supporting elements (<script>, <template>) | <li>, <tr> (a row inside a row) |
- Invalid
- Valid
<table>
<tr>
Name: Aisha <!-- loose text -->
<p>Age: 24</p> <!-- paragraph directly in the row -->
</tr>
</table>
<table>
<tr>
<td>Name: Aisha</td>
<td><p>Age: 24</p></td>
</tr>
</table>
If you need extra content, put it inside a cell.
Styling a Whole Rowβ
You cannot add a colspan-style attribute to <tr>, and old row attributes such as align, valign, and bgcolor are obsolete. Use CSS to style a row. The row becomes the target, and all its cells inherit the effect.
tr.highlight {
background: #fef9c3;
}
tbody tr:hover {
background: #f1f5f9;
}
<tr class="highlight">
<td>Today's session</td>
<td>10:30</td>
</tr>
A row hover effect helps readers follow a long row across a wide table.
Table Cells: <td> and <th>β
Each cell holds one piece of content.
| Element | Use it for | Default look |
|---|---|---|
<td> | Data | Regular text, left-aligned |
<th> | A label for a column or row | Bold, centered |
Both elements are cells. They sit in rows in the same way, and you can use both in the same row. A "row header" cell is a <th> at the start of a data row.
- HTML Code
- Browser Output
<table>
<tr>
<th>Product</th>
<th>Price</th>
<th>In stock</th>
</tr>
<tr>
<th>Notebook</th> <!-- a row header: labels the whole row -->
<td>3.50</td>
<td>Yes</td>
</tr>
<tr>
<th>Pen set</th>
<td>6.00</td>
<td>No</td>
</tr>
</table>
| Product | Price | In stock |
|---|---|---|
| Notebook | 3.50 | Yes |
| Pen set | 6.00 | No |
Here, the first column of the body uses <th>. It tells readers (and screen readers) that "Notebook" is the name of the row. You will see why in Table Headings.
What Can Go Inside a Cell?β
A cell can contain almost anything that can appear in the body of a page.
| Content | Example | Notes |
|---|---|---|
| Plain text and numbers | <td>42</td> | The most common |
| Inline formatting | <td><strong>Free</strong></td> | Bold, emphasis, code |
| Links | <td><a href="#">Read</a></td> | Use meaningful link text |
| Images | <td><img src="..." alt="..." /></td> | Always include alt |
| Lists | <td><ul>...</ul></td> | Good for several values |
| Paragraphs and line breaks | <td><p>...</p></td> | For longer text |
| Buttons and form fields | <td><button>...</button></td> | For editable data tables |
| Another table | <td><table>...</table></td> | Possible, but avoid it |
- HTML Code
- Browser Output
<table>
<tr>
<th>Course</th>
<th>Topics</th>
<th>Action</th>
</tr>
<tr>
<td><strong>Intro to HTML</strong></td>
<td>
<ul>
<li>Text</li>
<li>Links</li>
<li>Images</li>
</ul>
</td>
<td><a href="#">View course</a></td>
</tr>
</table>
| Course | Topics | Action |
|---|---|---|
| Intro to HTML |
| View course |
A table is for scanning. Short values (a word, a number, a short phrase) make a table easy to read. If a cell needs several paragraphs, ask whether a heading and text, or a list, would work better.
Why Nested Tables Are a Bad Ideaβ
Putting a table inside a cell is allowed, but screen readers announce each table separately, which gets confusing. If you find yourself nesting, try one of these instead:
- Split the content into two tables with their own captions.
- Use a list inside the cell.
- Use
rowspanorcolspanto merge cells (see Colspan and Rowspan).
Aligning Content in Cellsβ
Horizontal Alignmentβ
Use CSS text-align. The old align attribute is obsolete.
| Content type | Best alignment | Why |
|---|---|---|
| Text | Left (in left-to-right languages) | Easy to scan down |
| Numbers | Right | Digits line up, so you can compare quickly |
| Short codes, yes/no, icons | Center | Looks tidy |
Numbers are where alignment matters most. Compare these two tables.
- HTML + CSS
- Left vs Right
td.num,
th.num {
text-align: right;
font-variant-numeric: tabular-nums; /* every digit the same width */
}
<tr>
<td>Notebook</td>
<td class="num">3.50</td>
</tr>
<tr>
<td>Desk lamp</td>
<td class="num">124.00</td>
</tr>
Left-aligned numbers (hard to compare):
| Notebook | 3.50 |
| Desk lamp | 124.00 |
| Pen set | 16.25 |
Right-aligned numbers (easy to compare):
| Notebook | 3.50 |
| Desk lamp | 124.00 |
| Pen set | 16.25 |
With right alignment, the decimal points line up, and your eye can instantly see which number is biggest. Apply the same alignment to the column's header, so the label sits above its values.
Vertical Alignmentβ
When one cell is taller than its neighbors (for example, a list next to a single word), use vertical-align.
td {
vertical-align: top; /* top | middle | bottom */
}
top is usually the best choice for tables with mixed content, because each row then starts on the same line.
Controlling Wrapping and Widthβ
Browsers size columns automatically, based on the content. Long text wraps onto several lines. You can influence this with CSS.
| Goal | CSS |
|---|---|
| Keep a value on one line | white-space: nowrap; |
| Set a column width | width: 8rem; on a cell in that column |
| Set a maximum width | max-width: 20rem; |
| Break long words or URLs | overflow-wrap: anywhere; |
| Fixed, predictable columns | table-layout: fixed; on the table |
td.nowrap {
white-space: nowrap; /* good for dates, prices, codes */
}
table.fixed {
table-layout: fixed;
width: 100%;
}
Column widths follow the cells, so you only need to set a width on one cell per column, usually in the header row. For cleaner code, you can use <col> elements. You will learn that in Colgroup and Col.
Empty Cellsβ
Sometimes a value is missing. Always keep the cell, so the row has the right number of cells.
| Situation | What to write |
|---|---|
| No value exists yet | An empty <td></td>, or a dash β |
| Not applicable | N/A or "Not applicable" |
| Zero is a real value | 0 (do not leave blank) |
| Unknown | Unknown or ? with a note |
Empty cells can be styled with CSS:
td:empty {
background: #f1f5f9;
}
For screen reader users, a visible text such as "N/A" or "β" with a short note is clearer than a silent empty cell.
Putting It Together: A Price Listβ
- HTML Code
- Browser Output
<table>
<tr>
<th>Item</th>
<th class="num">Qty</th>
<th class="num">Price (USD)</th>
<th>Notes</th>
</tr>
<tr>
<td>Notebook</td>
<td class="num">3</td>
<td class="num">3.50</td>
<td></td>
</tr>
<tr>
<td>Desk lamp</td>
<td class="num">1</td>
<td class="num">124.00</td>
<td>Backorder, ships in 2 weeks</td>
</tr>
<tr>
<td>Pen set</td>
<td class="num">2</td>
<td class="num">16.25</td>
<td>N/A</td>
</tr>
</table>
| Item | Qty | Price (USD) | Notes |
|---|---|---|---|
| Notebook | 3 | 3.50 | |
| Desk lamp | 1 | 124.00 | Backorder, ships in 2 weeks |
| Pen set | 2 | 16.25 | N/A |
Notice three habits in this table: the unit "(USD)" is in the header, so cells hold only numbers; numbers are right-aligned; and the empty cell is still present.
A Peek at Cell Attributesβ
Cells have a few special attributes. You will study them later, so here is just a map:
| Attribute | Used on | Purpose | Lesson |
|---|---|---|---|
colspan | td, th | Stretch a cell across several columns | Colspan and Rowspan |
rowspan | td, th | Stretch a cell down several rows | Colspan and Rowspan |
scope | th | Says whether a header labels a column or a row | Table Headings |
headers | td, th | Links a cell to specific header cells by id | Table Accessibility |
abbr | th | A short version of a long heading | Table Accessibility |
Common Mistakes to Avoidβ
- Uneven rows. Every row needs the same number of cells (unless you merge on purpose).
- Loose text or other elements directly inside
<tr>. Everything belongs inside a cell. - Using
<td>for row or column labels. Use<th>. - Left-aligned numbers. Right-align numeric columns.
- Repeating units in every cell. Put "USD" or "kg" in the header once.
- Deleting empty cells. Keep them, with a placeholder when helpful.
- Using obsolete attributes such as
align,valign,bgcolor,width, andheighton cells. Use CSS. - Nesting tables. Try lists or merged cells instead.
- Stuffing long paragraphs into cells. Keep cells short.
- Forcing tiny fixed widths that make text overflow on small screens.
Practice Timeβ
Try these in your editor:
- Build a 4-column, 4-row table of products with a price column.
- Right-align the price column, including its header.
- Add
font-variant-numeric: tabular-numsand compare the effect. - Make the first column of the body
<th>cells. - Put a bulleted list in one cell and
vertical-align: topon all cells. - Add a row that highlights on hover with
tbody tr:hover. - Add an empty cell, then fill it with "N/A."
- Use
white-space: nowrapfor a date column. - Turn one cell into a link and another into an image with
alttext. - Bonus: remove one cell on purpose, and see how the row breaks. Fix it.
Quick Recapβ
Click a question to check your answer.
1. What can a <tr> contain directly?
Only <td> and <th> cells (plus script-supporting elements). Any other content must go inside a cell.
2. Where does a cell get its meaning?
From its position in the grid: the row it sits in and the column it sits under.
3. How should numbers be aligned, and why?
Right-aligned, with text-align: right, so the digits and decimal points line up and values are easy to compare.
4. What should you do when a value is missing?
Keep the cell, and leave it empty or add a placeholder such as "N/A" or "β".
5. How do you style a whole row?
Use CSS on the <tr> (a class, or tr:hover). Old row attributes like bgcolor and align are obsolete.
6. Can you mix <th> and <td> in the same row?
Yes. A <th> at the start of a row is a row header, and the rest of the row can be <td> data cells.
Key Takeawaysβ
- A table is a grid. Every cell's meaning comes from its row and its column.
- A
<tr>holds only cells. Everything else lives inside a<td>or<th>. - Cells can hold text, links, images, lists, and more, but short content makes the most readable tables.
- Right-align numbers, use
vertical-align: topfor mixed content, and control wrapping with CSS. - Keep every row the same length, use placeholders for empty cells, and put units in the headers.
Now you know how rows and cells work together. Next, learn how to label them properly in Table Headings, or return to the Tables overview.