WGU D276 practice

HTML Tables Explained: table, thead, tbody, tr, th, td, scope, caption and colspan/rowspan

A plain-English walkthrough of HTML table markup for WGU D276: how table, caption, thead, tbody, tr, th and td nest, what the scope attribute actually does for assistive technology, and how colspan and rowspan reshape the grid — plus practice questions with worked explanations.

How HTML table markup fits together

An HTML table is built from a strict nesting order: <table> holds row groups (<thead>, <tbody>, <tfoot>), each row group holds rows (<tr>), and each row holds cells — <th> for headers and <td> for data. On top of that skeleton, <caption> names the table, scope tells assistive technology which cells a header governs, and colspan/rowspan let one cell cover several grid positions.

Tables are for tabular data — information with a real row-and-column relationship, like a price list or a schedule. They are not a layout tool. Once you accept that, the markup starts making sense, because every element exists to describe a relationship rather than to push pixels around.

The required order inside <table>

The HTML Standard defines the content of <table> in this order: an optional <caption>, then zero or more <colgroup> elements, then an optional <thead>, then either <tbody> elements or bare <tr> elements, then an optional <tfoot>. So <caption> must be the very first child, and in current HTML the footer goes last — the old habit of writing <tfoot> before <tbody> is a leftover from HTML 4, which required that order so browsers could paint the footer before the data finished downloading.

Writing <tr> elements as direct children of <table> is allowed by that content model, but the parser still wraps them in a generated <tbody>. Your CSS and JavaScript see that generated <tbody> in the DOM, which is a classic source of "my selector doesn't match" confusion. Write the row groups yourself.

Headers, caption, and scope

Use <th> for any cell that labels other cells — column headings across the top and row labels down the side. The default browser stylesheet renders <th> bold and centered, but the real payoff is semantic: a screen reader can announce the relevant header along with the cell value, so a lone number like "30" arrives with the label that makes it mean something.

The scope attribute makes that association explicit. The HTML Standard defines it on <th> only — MDN lists it among the deprecated attributes of <td> — and it takes four keywords: col, row, colgroup, and rowgroup. If you omit it, or supply a value that is not one of those four, the attribute falls into its Auto state, where the browser selects the related cells from context. One catch worth knowing: scope="colgroup" is only conforming when the header is actually anchored in a column group, which means the table needs real <colgroup> elements. See MDN's <th> reference for the full list.

<caption> gives the table an accessible name so a screen reader user can decide whether to read further. It must stay first in the markup; if you want it under the table visually, use the CSS caption-side: bottom.

Spanning cells

colspan stretches a cell across columns and must be a whole number from 1 to 1000 — zero is not allowed. rowspan stretches a cell down rows, may go up to 65534, and uniquely accepts 0, which the standard defines as "span all the remaining rows in this row group." Every spanned position consumes a slot, so the following rows need fewer cells. Ready to practice? Work through the questions below, then keep going with the D276 practice sets.

Practice: Html Tables

  1. You need the table's title to appear visually below the table. Which approach is valid HTML?

    • Put <caption> as the first child of <table> and style it with caption-side: bottom
    • Put <caption> immediately after the closing </tbody> tag, still inside <table>
    • Put <caption> first and add the align="bottom" attribute to it
    • Put the title in a <th> inside <tfoot> instead, since <caption> can only render on top
    Show answer & explanation

    Correct answer: Put <caption> as the first child of <table> and style it with caption-side: bottom. The HTML Standard requires <caption> to be the first child of <table>; its visual position is a CSS concern, controlled by caption-side: top or bottom. Moving the element after </tbody> is the tempting answer because it matches where you want it to appear, but that violates the content model. The align attribute on <caption> is deprecated, and caption-side is its documented CSS replacement.

  2. In a sales table, the first cell of each body row holds the product name and labels the rest of that row. What is the correct markup for that cell?

    • <td scope="row">Widget</td>
    • <th scope="rowgroup">Widget</th>
    • <th scope="row">Widget</th>
    • <th headers="row">Widget</th>
    Show answer & explanation

    Correct answer: <th scope="row">Widget</th>. A cell that labels other cells is a header cell, so it must be <th>, and scope="row" states that it applies to the other cells in the same row. scope="rowgroup" is the near-miss: it would claim the header governs all the remaining cells in the entire <tbody>, not just one row. scope is deprecated on <td>, and headers takes the id values of header cells, not scope keywords.

  3. According to the HTML Standard, what does rowspan="0" do on a table cell?

    • It is invalid, so browsers fall back to rowspan="1"
    • It makes the cell span all the remaining rows in its row group
    • It removes the cell from the layout without deleting it from the DOM
    • It makes the cell span all the remaining columns in its row
    Show answer & explanation

    Correct answer: It makes the cell span all the remaining rows in its row group. The standard states that for rowspan the value zero means the cell is to span all the remaining rows in the row group (<thead>, <tbody>, or <tfoot>) it belongs to. Assuming it is invalid is the common trap, and it is a reasonable guess — because colspan="0" really is invalid, since colspan must be greater than zero and at most 1000. Spanning remaining columns is roughly what colspan="0" meant in HTML 4, not what rowspan does.

  4. A table has four columns. Row 1 is <td colspan="2">P</td><td>Q</td><td>R</td>. Row 2 is <td>S</td><td rowspan="2">T</td><td>U</td><td>V</td>. How many <td> elements does row 3 need to fill the remaining grid positions?

    • 2
    • 3
    • 4
    • 1
    Show answer & explanation

    Correct answer: 3. Cell T occupies the second column in both row 2 and row 3, so row 3 already has one of its four slots filled and needs cells only for columns 1, 3, and 4 — three <td> elements. Answering 4 is the natural mistake: it counts the table's total columns and forgets that a rowspan from an earlier row consumes a slot. The colspan in row 1 does not affect row 3 at all, since colspan only spreads sideways within its own row.

  5. Which sequence of children inside <table> matches the content model in the current HTML Standard?

    • <thead>, <caption>, <tbody>, <tfoot>
    • <caption>, <thead>, <tfoot>, <tbody>
    • <caption>, <colgroup>, <thead>, <tbody>, <tfoot>
    • <caption>, <tbody>, <thead>, <tfoot>
    Show answer & explanation

    Correct answer: <caption>, <colgroup>, <thead>, <tbody>, <tfoot>. The spec order is an optional <caption>, then any <colgroup> elements, then an optional <thead>, then the body rows, then an optional <tfoot>. Placing <tfoot> before <tbody> is the strongest distractor because HTML 4 actually required that order, and browsers still render it — but it is no longer conforming markup. <caption> can never follow <thead>, and <thead> must precede the body rows.

  6. You wrote a table with <tr> elements placed directly inside <table>, with no row group. Your rule table > tr { background: #eee; } never applies to anything. What explains this?

    • The parser wraps those rows in a generated <tbody>, so in the DOM they are children of that <tbody> rather than of <table>
    • Placing <tr> directly inside <table> is invalid, so the browser discards the rows entirely
    • The parser moves stray rows into a generated <thead>, which the selector does not match
    • The child combinator (>) is not supported when the parent is a table element
    Show answer & explanation

    Correct answer: The parser wraps those rows in a generated <tbody>, so in the DOM they are children of that <tbody> rather than of <table>. The HTML parser inserts a <tbody> around rows that appear directly inside <table>, and that generated element is a real node in the DOM, so table > tr matches nothing while table > tbody > tr matches everything. Calling the markup invalid is the tempting answer, but the content model actually permits bare <tr> children — the rows are conforming and are never discarded. The generated wrapper is always a <tbody>, never a <thead>, and the child combinator works normally here.

A worked example

Here is a small table that uses every piece correctly. Read it as a grid: two header rows, two body rows, one footer row, three columns. The numbers are invented sample data, there only to give the cells something to hold.

<table>
  <caption>Quarterly sales by region</caption>
  <colgroup></colgroup>
  <colgroup span="2"></colgroup>
  <thead>
    <tr>
      <th scope="col" rowspan="2">Region</th>
      <th scope="colgroup" colspan="2">Quarter 1</th>
    </tr>
    <tr>
      <th scope="col">Units</th>
      <th scope="col">Revenue</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">North</th>
      <td>120</td>
      <td>$9,600</td>
    </tr>
    <tr>
      <th scope="row">South</th>
      <td>95</td>
      <td>$7,125</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row">Total</th>
      <td>215</td>
      <td>$16,725</td>
    </tr>
  </tfoot>
</table>

Notice the second header row contains only two cells. The Region header already claimed column 1 for both header rows through rowspan="2", so writing a third cell there would push the table one column wider than intended.

Why the <colgroup> elements are there

Most tutorials show scope="colgroup" on a spanning header in a table that has no <colgroup> at all. The HTML Standard is stricter than that: a <th> element's scope attribute must not be in the Column Group state if the element is not anchored in a column group, and column groups are created only by <colgroup> elements — nothing generates them implicitly. Row groups are the opposite case, since the parser supplies a <tbody> whether you write one or not, so scope="rowgroup" is always anchored.

That is why the example declares one column group for the Region column and a second, two-column group for the Quarter 1 pair. If you would rather not manage column groups, scope="col" on the spanning header is the simpler conforming choice: the Column state applies to subsequent cells in the same column or columns, so it still covers both columns the header stretches over.

Mistakes people actually make

  • Bolding a <td> with CSS instead of using <th>. It looks identical and carries none of the meaning. Choose the element by role, then style it.
  • Putting scope on <td>. The standard defines the attribute on <th> only, and MDN lists it as deprecated on <td>.
  • Writing <tfoot> before <tbody>. Browsers forgive it, the content model does not.
  • Omitting <tbody> and then wondering why table > tr never matches. The parser inserted a <tbody> between them.
  • Reaching for the summary attribute. It is deprecated on <table>; use <caption>, or a paragraph next to the table, to describe the data.

How to reason about the tricky cases

When a span question looks confusing, sketch the grid on paper and cross off occupied squares row by row. A colspan only consumes slots in its own row; a rowspan reaches into the rows below and takes a slot from each. Count what is left, and the number of cells to write becomes obvious.

For scope, ask one question: which cells does this label describe? One column below it means col. One row beside it means row. A declared column group means colgroup. All the rows in a <tbody> means rowgroup. If a table is so layered that no single answer fits, that is the signal to switch to the id and headers pairing described in the MDN table accessibility guide — or, more often, to split one overloaded table into two simple ones, which is what the W3C's own tutorial recommends for genuinely complex data.

For the rest of the markup and accessibility topics in this course, see the D276 Web Development Foundations study guide.

Want someone to walk you through this?

Book 1-on-1 prep for WGU D276 — original coaching, never exam content.

Stuck on this topic? WhatsApp us on +1 646 980 4914.

Working through the whole course? Read the full WGU D276 study guide, or see every WGU D276 practice topic.

More WGU D276 practice

Html Document Structure

The five lines at the top of every HTML file are not decoration. Here is what the doctype, the html/head/body split, meta charset, and the viewport tag each do — why the browser misbehaves when you leave one out, and how to reason about the cases that trip people up.

Semantic Structure Tags

Semantic structure tags describe what a block of content is, not how it looks. This lesson explains the difference between article, section, aside, main, nav, header and footer — including the syndication test that settles article vs. section, the strict placement rules for main, and how header and footer change meaning depending on where they sit — then gives you practice questions with worked explanations.

Headings Text Links Lists

Heading elements rank content instead of sizing it, strong and em carry meaning that b and i do not, a fragment link points at an element's id, and ul, ol, and dl each describe a different kind of grouping. Work through how each one behaves, then test yourself on the cases people actually get wrong.

Images And Alt Text

An image starts with one element and one honest description. From there, srcset, sizes, and picture answer a single question: which file should the browser download on this screen? This guide walks through alt text, figure and figcaption, resolution switching, and art direction, then gives you six practice questions with full explanations.

Html5 Audio And Video

The audio and video elements embed media without a plugin. Learn how the browser chooses among source children, which MIME types to use for MP4, WebM, and MP3, what controls and preload actually do, and why fallback content between the tags is not an error message. Includes practice questions with full explanations.

Forms And Input Types

A form's action says where the data goes and its method says how it travels there. Work through the pieces that carry the data — name attributes, input types, labels tied to ids, select options, textarea content and the button that submits it all — then test yourself on the details that trip people up.