HTML <table> reference Jump to this section

Common attributes Jump to this section

Basic table structure Jump to this section

A <table> element represents tabular data. It consists of rows (<tr>), header cells (<th>), and data cells (<td>). Use semantic structure with <thead>, <tbody>, and optionally <tfoot>.

Name Age
Alice 28
Bob 34
<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Age</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Alice</td>
            <td>28</td>
        </tr>
        <tr>
            <td>Bob</td>
            <td>34</td>
        </tr>
    </tbody>
</table>

Table elements hierarchy Jump to this section

<table>
  ├── <caption>       (optional: table title)
  ├── <colgroup>      (optional: column groups)
  │   └── <col>       (column styling)
  ├── <thead>         (table header)
  │   └── <tr>        (table row)
  │       ├── <th>    (header cell)
  │       └── <th>
  ├── <tbody>         (table body)
  │   └── <tr>
  │       ├── <td>    (data cell)
  │       └── <td>
  └── <tfoot>         (optional: table footer)
      └── <tr>
          ├── <td>
          └── <td>

Table attributes Jump to this section

border (deprecated) Jump to this section

Note: This attribute is deprecated. Use CSS border instead.

<!-- Don't use -->
<table border="1">
<!-- Use CSS instead -->
<table style="border: 1px solid #ddd;">

Caption element Jump to this section

<caption> Jump to this section

Provides a title or description for the table. Should be the first child of <table>.

Employee Data
Name Role
Alice Developer
<table>
    <caption>Employee Data</caption>
    <thead>
        <tr>
            <th>Name</th>
            <th>Role</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Alice</td>
            <td>Developer</td>
        </tr>
    </tbody>
</table>

Accessibility: Always use <caption> to describe the table's purpose for screen readers.

Header cell attributes (<th>) Jump to this section

scope Jump to this section

Specifies which cells the header applies to.

Values:

  • row: Header for a row
  • col: Header for a column (default)
  • rowgroup: Header for a row group
  • colgroup: Header for a column group
Product Price
Apple $1.00
Banana $0.50
<table>
    <thead>
        <tr>
            <th scope="col">Product</th>
            <th scope="col">Price</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">Apple</th>
            <td>$1.00</td>
        </tr>
    </tbody>
</table>

Best practice: Always use scope on <th> elements for accessibility.

abbr Jump to this section

Provides an abbreviated version of the header content for screen readers.

<th scope="col" abbr="Emp ID">Employee Identification Number</th>

Cell attributes (<td> and <th>) Jump to this section

colspan Jump to this section

Spans a cell across multiple columns.

Name Age
First Last
Alice Smith 28
<table>
    <thead>
        <tr>
            <th colspan="2">Name</th>
            <th>Age</th>
        </tr>
        <tr>
            <th>First</th>
            <th>Last</th>
            <th></th>
        </tr>
    </thead>
</table>

rowspan Jump to this section

Spans a cell across multiple rows.

Alice Email alice@example.com
Phone 555-1234
<table>
    <tbody>
        <tr>
            <td rowspan="2">Alice</td>
            <td>Email</td>
            <td>alice@example.com</td>
        </tr>
        <tr>
            <td>Phone</td>
            <td>555-1234</td>
        </tr>
    </tbody>
</table>

headers Jump to this section

References header cells by their id for complex tables. Improves accessibility.

<table>
    <thead>
        <tr>
            <th id="name">Name</th>
            <th id="email">Email</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td headers="name">Alice</td>
            <td headers="email">alice@example.com</td>
        </tr>
    </tbody>
</table>

Column elements Jump to this section

<colgroup> and <col> Jump to this section

Group and style columns without affecting content structure.

Name Age City
Alice 28 NYC
<table>
    <colgroup>
        <col style="background: #f9fafb;">
        <col style="background: #eff6ff;">
        <col style="background: #f0fdf4;">
    </colgroup>
    <thead>
        <tr>
            <th>Name</th>
            <th>Age</th>
            <th>City</th>
        </tr>
    </thead>
</table>

span (col) Jump to this section

Specifies how many columns the <col> element spans.

<colgroup>
    <col span="2" style="background: #f9fafb;">
    <col style="background: #eff6ff;">
</colgroup>

Accessibility attributes Jump to this section

role Jump to this section

Tables have an implicit table role. Usually not needed unless changing semantics.

<!-- Presentation table (layout, not data) -->
<table role="presentation">
    <!-- Content -->
</table>

aria-label Jump to this section

Provides an accessible label for the table.

<table aria-label="Employee contact information">
    <!-- Content -->
</table>

aria-describedby Jump to this section

References additional descriptive text.

<table aria-describedby="table-description">
    <!-- Content -->
</table>
<p id="table-description">
    This table shows quarterly sales data for 2023.
</p>

aria-sort Jump to this section

Indicates the current sort order on a column header.

Values: ascending, descending, none, other

<th scope="col" aria-sort="ascending">Name ↑</th>
<th scope="col" aria-sort="none">Age</th>

Common attributes Jump to this section

id Jump to this section

Unique identifier for the table or cells.

<table id="employee-table">
    <!-- Content -->
</table>
<th id="name-header">Name</th>

class Jump to this section

CSS class names for styling.

<table class="data-table striped bordered">
    <!-- Content -->
</table>
<tr class="highlight">
    <td>Important row</td>
</tr>

data-* attributes Jump to this section

Custom data attributes.

<table data-table-id="12345" data-sortable="true">
    <!-- Content -->
</table>
<tr data-user-id="101" data-status="active">
    <td>Alice</td>
</tr>
<script>
  const table = document.querySelector('table');
  const tableId = table.dataset.tableId; // "12345"
</script>

title Jump to this section

Tooltip on hover (not accessible on mobile).

<td title="Click to sort">Name</td>

Table sections Jump to this section

<thead> Jump to this section

Groups header rows. Should contain <tr> with <th> cells.

<thead>
    <tr>
        <th>Column 1</th>
        <th>Column 2</th>
    </tr>
</thead>

Benefits:

  • Semantic structure
  • Can be styled separately
  • Repeats on printed pages in some browsers
  • Fixed header positioning with CSS

<tbody> Jump to this section

Groups body rows. Contains the main table data.

<tbody>
    <tr>
        <td>Data 1</td>
        <td>Data 2</td>
    </tr>
</tbody>

Note: If <tbody> is not explicitly included, the browser will create it automatically.

<tfoot> Jump to this section

Groups footer rows. Typically for totals or summaries.

Item Price
Apple $1.00
Banana $0.50
Total $1.50
<table>
    <thead>
        <tr>
            <th>Item</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Apple</td>
            <td>$1.00</td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <th>Total</th>
            <td>$1.50</td>
        </tr>
    </tfoot>
</table>

Note: <tfoot> can be placed before <tbody> in HTML but will render at the bottom.

Best practices Jump to this section

Use semantic structure Jump to this section

Always use <thead>, <tbody>, and when appropriate <tfoot>.

<!-- Good -->
<table>
    <thead>
        <tr><th>Name</th><th>Age</th></tr>
    </thead>
    <tbody>
        <tr><td>Alice</td><td>28</td></tr>
    </tbody>
</table>
<!-- Avoid -->
<table>
    <tr><th>Name</th><th>Age</th></tr>
    <tr><td>Alice</td><td>28</td></tr>
</table>

Use <th> for headers Jump to this section

Use <th> for header cells, not <td> with bold styling.

<!-- Good -->
<tr>
    <th scope="col">Name</th>
    <th scope="col">Age</th>
</tr>
<!-- Avoid -->
<tr>
    <td><strong>Name</strong></td>
    <td><strong>Age</strong></td>
</tr>

Include <caption> or aria-label Jump to this section

Provide a title or description for accessibility.

<table>
    <caption>Monthly Sales Report - Q4 2023</caption>
    <!-- Content -->
</table>
<!-- Or -->
<table aria-label="Monthly Sales Report">
    <!-- Content -->
</table>

Use scope attribute Jump to this section

Always add scope to <th> elements.

<th scope="col">Column Header</th>
<th scope="row">Row Header</th>

Don't use tables for layout Jump to this section

Tables are for tabular data only. Use CSS Grid or Flexbox for page layout.

<!-- Bad: Using table for layout -->
<table>
    <tr>
        <td>Sidebar</td>
        <td>Main content</td>
    </tr>
</table>
<!-- Good: Use CSS Grid -->
<div class="layout">
    <aside>Sidebar</aside>
    <main>Main content</main>
</div>

Complex table examples Jump to this section

Multi-level headers Jump to this section

Name Contact
Email Phone
Alice alice@example.com 555-1234
<table>
    <thead>
        <tr>
            <th rowspan="2">Name</th>
            <th colspan="2">Contact</th>
        </tr>
        <tr>
            <th>Email</th>
            <th>Phone</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Alice</td>
            <td>alice@example.com</td>
            <td>555-1234</td>
        </tr>
    </tbody>
</table>

With row headers Jump to this section

<table>
    <thead>
        <tr>
            <th></th>
            <th scope="col">2021</th>
            <th scope="col">2022</th>
            <th scope="col">2023</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">Sales</th>
            <td>$100K</td>
            <td>$150K</td>
            <td>$200K</td>
        </tr>
        <tr>
            <th scope="row">Profit</th>
            <td>$20K</td>
            <td>$35K</td>
            <td>$50K</td>
        </tr>
    </tbody>
</table>

Grouped rows Jump to this section

<table>
    <thead>
        <tr>
            <th>Department</th>
            <th>Employee</th>
            <th>Role</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th rowspan="2" scope="rowgroup">Engineering</th>
            <td>Alice</td>
            <td>Developer</td>
        </tr>
        <tr>
            <td>Bob</td>
            <td>Designer</td>
        </tr>
    </tbody>
    <tbody>
        <tr>
            <th rowspan="2" scope="rowgroup">Marketing</th>
            <td>Carol</td>
            <td>Manager</td>
        </tr>
        <tr>
            <td>Dave</td>
            <td>Analyst</td>
        </tr>
    </tbody>
</table>

Styling basics Jump to this section

Tables can be styled extensively with CSS to create readable, attractive data presentations.

Basic table styling Jump to this section

Name Age
Alice 28
Bob 34
table {
    border-collapse: collapse;
    width: 100%;
}
th, td {
    padding: 12px;
    border: 1px solid #e5e7eb;
    text-align: left;
}
th {
    background: #f9fafb;
    font-weight: 600;
}

Striped rows (zebra) Jump to this section

Name Role
Alice Developer
Bob Designer
Carol Manager
.table-striped tbody tr:nth-child(even) {
    background: #f9fafb;
}
/* Or odd rows */
.table-striped tbody tr:nth-child(odd) {
    background: #f9fafb;
}

Hover effect Jump to this section

Name City
Alice NYC
Bob LA
.table-hover tbody tr {
    transition: background 0.2s;
}
.table-hover tbody tr:hover {
    background: #eff6ff;
}

Borderless table Jump to this section

Name Status
Alice Active
Bob Away
.table-borderless {
    border-collapse: collapse;
}
.table-borderless thead tr {
    border-bottom: 2px solid #e5e7eb;
}
.table-borderless tbody tr {
    border-bottom: 1px solid #f3f4f6;
}
.table-borderless th,
.table-borderless td {
    padding: 12px;
    border: none;
}

Compact table Jump to this section

ID Name
001 Alice
002 Bob
.table-compact {
    font-size: 13px;
}
.table-compact th,
.table-compact td {
    padding: 6px 8px;
}

Rounded corners Jump to this section

Name Age
Alice 28
Bob 34
.table-rounded {
    border-collapse: separate;
    border-spacing: 0;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    overflow: hidden;
}

With shadow Jump to this section

Name Role
Alice Developer
Bob Designer
.table-shadow {
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    border-radius: 8px;
    overflow: hidden;
}

Colored headers Jump to this section

Name Status
Alice Active
Bob Away
.table-colored thead {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
}
/* Or solid colors */
.table-blue thead {
    background: #3b82f6;
    color: white;
}
.table-green thead {
    background: #10b981;
    color: white;
}
Name Age
Alice28
Bob34
Carol25
Dave42
Eve31
.table-sticky thead {
    position: sticky;
    top: 0;
    z-index: 10;
    background: white;
}
/* With container */
.table-container {
    max-height: 400px;
    overflow-y: auto;
}

Responsive horizontal scroll Jump to this section

Name Email Phone City
Alice alice@example.com 555-1234 NYC
← Scroll horizontally →
.table-responsive {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.table-responsive table {
    min-width: 600px;
}

Card-style table Jump to this section

NameRole
Alice Developer
Bob Designer
.table-cards {
    border-collapse: separate;
    border-spacing: 0 12px;
}
.table-cards tbody tr {
    background: white;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    border-radius: 8px;
}
.table-cards td {
    padding: 16px;
}
.table-cards td:first-child {
    border-radius: 8px 0 0 8px;
}
.table-cards td:last-child {
    border-radius: 0 8px 8px 0;
}

With status badges Jump to this section

Name Status
Alice Active
Bob Away
Carol Offline
.badge {
    display: inline-block;
    padding: 4px 12px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 500;
}
.badge-success {
    background: #dcfce7;
    color: #166534;
}
.badge-warning {
    background: #fef3c7;
    color: #92400e;
}
.badge-danger {
    background: #fee2e2;
    color: #991b1b;
}

Alternating column colors Jump to this section

Q1 Q2 Q3 Q4
$100K $120K $150K $180K
/* Using colgroup */
col:nth-child(odd) {
    background: #f9fafb;
}
/* Or using nth-child on cells */
td:nth-child(odd),
th:nth-child(odd) {
    background: #f9fafb;
}

Mobile responsive (stack) Jump to this section

Name: Alice Age: 28 City: NYC
Name: Bob Age: 34 City: LA
@media (max-width: 768px) {
    .table-mobile {
        display: block;
    }
    .table-mobile thead {
        display: none;
    }
    .table-mobile tbody {
        display: block;
    }
    .table-mobile tr {
        display: flex;
        flex-direction: column;
        margin-bottom: 12px;
        border: 1px solid #e5e7eb;
        border-radius: 8px;
        padding: 12px;
    }
    .table-mobile td {
        display: block;
        padding: 4px 0;
    }
    .table-mobile td::before {
        content: attr(data-label) ': ';
        font-weight: 600;
    }
}

JavaScript patterns Jump to this section

Enhance tables with sorting, filtering, pagination, and interactive features using JavaScript.

Sort table columns Jump to this section

Name ↕ Age ↕
Alice28
Charlie22
Bob34
function sortTable(columnIndex) {
    const table = document.getElementById('sortable-table');
    const tbody = table.querySelector('tbody');
    const rows = Array.from(tbody.querySelectorAll('tr'));
    // Toggle sort direction
    const isAscending = tbody.dataset.sortDir !== 'asc';
    tbody.dataset.sortDir = isAscending ? 'asc' : 'desc';
    rows.sort((a, b) => {
        const aText = a.cells[columnIndex].textContent;
        const bText = b.cells[columnIndex].textContent;
        // Try numeric comparison
        const aNum = parseFloat(aText);
        const bNum = parseFloat(bText);
        if (!isNaN(aNum) && !isNaN(bNum)) {
            return isAscending ? aNum - bNum : bNum - aNum;
        }
        // String comparison
        return isAscending 
            ? aText.localeCompare(bText)
            : bText.localeCompare(aText);
    });
    // Re-append sorted rows
    rows.forEach(row => tbody.appendChild(row));
}

Filter table rows Jump to this section

Name Role
AliceDeveloper
BobDesigner
CarolManager
const filterInput = document.getElementById('filter-input');
const table = document.getElementById('filter-table');
const rows = table.querySelectorAll('tbody tr');
filterInput.addEventListener('input', (e) => {
    const searchTerm = e.target.value.toLowerCase();
    rows.forEach(row => {
        const text = row.textContent.toLowerCase();
        if (text.includes(searchTerm)) {
            row.style.display = '';
        } else {
            row.style.display = 'none';
        }
    });
});

Add row dynamically Jump to this section

Name Age
Alice28
function addRow() {
    const table = document.getElementById('add-row-table');
    const tbody = table.querySelector('tbody');
    const newRow = tbody.insertRow();
    const cell1 = newRow.insertCell(0);
    const cell2 = newRow.insertCell(1);
    cell1.textContent = 'New Person';
    cell2.textContent = '25';
    // Or with innerHTML
    // newRow.innerHTML = 'New Person25';
}

Delete row Jump to this section

Name Action
Alice
Bob
function deleteRow(button) {
    const row = button.closest('tr');
    row.remove();
}
// Or by index
function deleteRowByIndex(tableId, rowIndex) {
    const table = document.getElementById(tableId);
    table.deleteRow(rowIndex);
}

Row selection Jump to this section

Name
Alice
Bob
0 selected
function toggleRowSelection(row) {
    const checkbox = row.querySelector('.row-checkbox');
    checkbox.checked = !checkbox.checked;
    if (checkbox.checked) {
        row.style.background = '#eff6ff';
    } else {
        row.style.background = '';
    }
    updateSelectionCount();
}
function updateSelectionCount() {
    const checked = document.querySelectorAll('.row-checkbox:checked');
    const count = document.getElementById('selection-count');
    count.textContent = `${checked.length} selected`;
}
// Select all
const selectAll = document.getElementById('select-all');
selectAll.addEventListener('change', (e) => {
    const checkboxes = document.querySelectorAll('.row-checkbox');
    checkboxes.forEach(cb => {
        cb.checked = e.target.checked;
        const row = cb.closest('tr');
        row.style.background = cb.checked ? '#eff6ff' : '';
    });
    updateSelectionCount();
});

Pagination Jump to this section

Name ID
Alice1
Bob2
Carol3
Dave4
Eve5
Page 1
let currentPage = 1;
const rowsPerPage = 2;
function showPage(page) {
    const table = document.getElementById('paginated-table');
    const tbody = table.querySelector('tbody');
    const rows = tbody.querySelectorAll('tr');
    const start = (page - 1) * rowsPerPage;
    const end = start + rowsPerPage;
    rows.forEach((row, index) => {
        if (index >= start && index < end) {
            row.style.display = '';
        } else {
            row.style.display = 'none';
        }
    });
    const totalPages = Math.ceil(rows.length / rowsPerPage);
    document.getElementById('page-info').textContent = 
        `Page ${page} of ${totalPages}`;
}
function changePage(direction) {
    const table = document.getElementById('paginated-table');
    const rows = table.querySelectorAll('tbody tr');
    const totalPages = Math.ceil(rows.length / rowsPerPage);
    currentPage += direction;
    currentPage = Math.max(1, Math.min(currentPage, totalPages));
    showPage(currentPage);
}
showPage(1); // Initial load

Editable cells Jump to this section

Name Age
Alice 28
Bob 34
// Make cells editable with contenteditable
const cells = document.querySelectorAll('#editable-table td');
cells.forEach(cell => {
    cell.setAttribute('contenteditable', 'true');
    // Save on blur
    cell.addEventListener('blur', () => {
        console.log('Saved:', cell.textContent);
        // Send to server, update state, etc.
    });
    // Highlight on focus
    cell.addEventListener('focus', () => {
        cell.style.background = '#eff6ff';
    });
    cell.addEventListener('blur', () => {
        cell.style.background = '';
    });
});

Export to CSV Jump to this section

Name Age City
Alice28NYC
Bob34LA
function exportToCSV() {
    const table = document.getElementById('export-table');
    const rows = table.querySelectorAll('tr');
    let csv = [];
    rows.forEach(row => {
        const cells = row.querySelectorAll('td, th');
        const rowData = Array.from(cells)
            .map(cell => `"${cell.textContent}"`)
            .join(',');
        csv.push(rowData);
    });
    const csvContent = csv.join('\n');
    // Download
    const blob = new Blob([csvContent], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'table-export.csv';
    link.click();
    URL.revokeObjectURL(url);
}

Calculate totals Jump to this section

Item Price
Apple10
Banana5
Orange8
Total $0
function calculateTotal() {
    const table = document.getElementById('total-table');
    const rows = table.querySelectorAll('tbody tr');
    let total = 0;
    rows.forEach(row => {
        const priceCell = row.cells[1];
        const price = parseFloat(priceCell.textContent);
        if (!isNaN(price)) {
            total += price;
        }
    });
    document.getElementById('total-cell').textContent = 
        '$' + total.toFixed(2);
}

Highlight row on click Jump to this section

Name Role
Alice Developer
Bob Designer
function highlightRow(row) {
    // Remove highlight from all rows
    const table = row.closest('table');
    const rows = table.querySelectorAll('tbody tr');
    rows.forEach(r => {
        r.style.background = '';
        r.classList.remove('selected');
    });
    // Highlight clicked row
    row.style.background = '#dbeafe';
    row.classList.add('selected');
}

Load data from JSON Jump to this section

Name Email
No data
function loadJSON() {
    // Sample data (could be from API)
    const data = [
        { name: 'Alice', email: 'alice@example.com' },
        { name: 'Bob', email: 'bob@example.com' },
        { name: 'Carol', email: 'carol@example.com' }
    ];
    const table = document.getElementById('json-table');
    const tbody = table.querySelector('tbody');
    tbody.innerHTML = '';
    data.forEach(item => {
        const row = tbody.insertRow();
        const cell1 = row.insertCell(0);
        const cell2 = row.insertCell(1);
        cell1.textContent = item.name;
        cell2.textContent = item.email;
    });
}
// Or from API
async function loadFromAPI() {
    const response = await fetch('/api/users');
    const data = await response.json();
    // Populate table with data
}