{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.
The <caption> element provides an accessible title for the table.",
"task": "Create a simple table with:
1. A <caption> saying Fruit Prices
2. A header row with Fruit and Price columns
3. At least 2 data rows",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "
\n Fruit Prices\n \n | Fruit | \n Price | \n
\n \n | Apple | \n $1.50 | \n
\n \n | Banana | \n $0.75 | \n
\n
",
"previewContainer": "preview-area",
"concept": {
"explanation": "HTML tables communicate semantic data relationships through row/column structure, enabling screen readers to navigate two-dimensionally (announcing \"row 2, column 1: Apple\" or \"Fruit column, row 2\") instead of linearly reading cells. The th (header cell) vs td (data cell) distinction creates accessibility associations: screen readers remember headers and announce them when navigating data cells, giving context. Caption provides a programmatic table title that screen readers announce before entering the table structure. The table element has implicit ARIA role=\"table\", and browsers expose table semantics through accessibility APIs, allowing AT users to jump between tables, skip table content, or navigate by row/column.",
"diagram": "Table Semantic Structure\n\n\n Fruit Prices\n ← Row 1 (header)\n | Fruit | ← Column 1 header\n Price | ← Column 2 header\n
\n ← Row 2 (data)\n | Apple | ← Data cell\n $1.50 | \n
\n
\n\nScreen Reader Navigation:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\"Table, Fruit Prices\"\n\"2 columns, 2 rows\"\n\nEnter table:\n\"Row 1, Column 1: Fruit, header\"\n→ Right arrow\n\"Row 1, Column 2: Price, header\"\n→ Down arrow\n\"Row 2, Column 2: $1.50\"\n(Still remembers \"Price\" header)\n\nHeader Association:\n┌─────────┬─────────┐\n│ Fruit │ Price │ ← th elements\n├─────────┼─────────┤\n│ Apple │ $1.50 │\n└─────────┴─────────┘\n ↑ ↑\n └─────────┘\n When SR focuses on\n \"$1.50\", it announces:\n \"Price: $1.50, row 2\"\n\nvs div Table:\n✗ No semantic structure\n✗ Linear reading only\n✗ No header association"
},
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <table> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <caption> for the table title"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.
You can also use <tfoot> for footer rows like totals.",
"task": "Create a structured table:
1. A <caption> with Monthly Sales
2. A <thead> with Month and Revenue headers
3. A <tbody> with at least 2 data rows",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "\n Monthly Sales\n \n \n | Month | \n Revenue | \n
\n \n \n \n | January | \n $12,500 | \n
\n \n | February | \n $14,200 | \n
\n \n
",
"previewContainer": "preview-area",
"concept": {
"explanation": "The thead/tbody/tfoot elements create logical sections that browsers can optimize for printing (repeating headers on each page), scrolling (sticky headers while tbody scrolls), and accessibility (screen readers announce section boundaries). This grouping also enables CSS to style sections differently without classes—tbody tr:hover works naturally. Some browsers display thead/tfoot with distinct styling by default. Screen readers announce section transitions (\"entering table header\", \"entering table body\") helping users understand where they are in large tables. For very long tables, browsers may keep thead fixed while scrolling tbody, and when printing multi-page tables, browsers repeat thead at the top of each printed page automatically.",
"diagram": "Table Section Structure\n\n\n Sales Data\n ← Header section\n | Month |
\n \n ← Data section\n | Jan |
\n | Feb |
\n ...(many rows)\n \n ← Footer section\n | Total |
\n \n
\n\nPrinting Long Tables:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nPage 1:\n┌─────────────┐\n│ Month │ ← thead (repeated)\n├─────────────┤\n│ Jan │\n│ Feb │ tbody continues...\n\nPage 2:\n┌─────────────┐\n│ Month │ ← thead (repeated)\n├─────────────┤\n│ Mar │\n│ Apr │ tbody continues...\n\nScrolling Long Tables:\n┌─────────────────────────────┐\n│ Month │ Revenue │ Fixed │ ← thead sticky\n├───────────┴─────────┴───────┤\n│ Jan │ $10,000 │ ↕\n│ Feb │ $12,000 │ Scrolls\n│ Mar │ $11,500 │ ↕\n│ ... │\n└─────────────────────────────┘\n\nScreen Reader:\n\"Entering table header\"\n→ Reads headers\n\"Entering table body\"\n→ Reads data rows\n\"Entering table footer\"\n→ Reads totals"
},
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <table> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <caption> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <thead> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <tbody> for the data rows"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 data rows in tbody"
}
]
},
{
"id": "table-complete",
"title": "Complete Table with Footer",
"description": "Add <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.
Combine all sections for a fully structured, accessible table.",
"task": "Create a complete table:
1. A <caption> with Order Summary
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> with a Total row",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "\n Order Summary\n \n \n | Item | \n Price | \n
\n \n \n \n | Widget | \n $25.00 | \n
\n \n | Gadget | \n $35.00 | \n
\n \n \n \n | Total | \n $60.00 | \n
\n \n
",
"previewContainer": "preview-area",
"concept": {
"explanation": "The tfoot element defines summary or calculation rows that semantically belong at the table's end, even though in HTML source order it can appear before tbody (browsers render it at the bottom regardless). This location flexibility is historical—placing tfoot before tbody in source allows browsers to render footers before receiving all body data, useful for streaming large datasets. Screen readers announce tfoot as \"table footer\" when entering, signaling that this row contains aggregate data rather than individual records. Tfoot is ideal for totals, averages, counts, or any row that summarizes the data above—it gives these special rows semantic meaning that plain tbody rows lack.",
"diagram": "Tfoot Source Order Flexibility\n\nHTML Source Order (optional):\n\n ...\n ... ← Before tbody\n ...\n
\n\nBrowser Renders:\n┌─────────────────────────────┐\n│ thead (headers) │\n├─────────────────────────────┤\n│ tbody (data rows) │\n│ ... │\n├─────────────────────────────┤\n│ tfoot (totals) │ ← Rendered last\n└─────────────────────────────┘\n\nWhy Allow tfoot Before tbody?\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nStreaming Large Datasets:\n1. Send \n2. Send \n3. Stream (may take time)\n→ Footer renders before all data\n\nSemantic Meaning:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n → Individual records\n → Aggregate summary\n\nScreen Reader:\n\"Table footer, row 1\"\n\"Total, $60.00\"\n\nCommon tfoot Content:\n✓ Totals/Subtotals\n✓ Averages\n✓ Record counts\n✓ Summary calculations\n\n✗ Not for regular data rows\n✗ Not for pagination controls"
},
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <table> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <caption> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <thead> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <tbody> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <tfoot> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
}
]
}
]
}