Create an Expandable Table with Collapsible Rows Using HTML, CSS, and JavaScript

In many projects, displaying large datasets in a clean and user-friendly way can be a challenge. Too much information on one screen can overwhelm users and make navigation difficult.

A great solution is an expandable table with collapsible rows. This allows users to view a summary at first and expand only the rows they’re interested in for more details. It’s ideal for dashboards, reports, and data-heavy applications.

In this article, I’ll show you how to build this interactive table using HTML, CSS, and JavaScript. We’ll look at a few simple approaches so you can choose the one that works best for your project.

Method 1 – Basic Expandable Table Using Pure JavaScript

Let’s start with a simple implementation that uses minimal code:

Read Python Django Format Date

Step 1: Create the HTML Structure

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Expandable Table</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h1>U.S. State Population Data</h1>
        <table id="expandableTable">
            <thead>
                <tr>
                    <th></th>
                    <th>State</th>
                    <th>Population</th>
                    <th>Region</th>
                </tr>
            </thead>
            <tbody>
                <tr class="parent-row" data-id="1">
                    <td><span class="expand-btn">+</span></td>
                    <td>California</td>
                    <td>39.5M</td>
                    <td>West</td>
                </tr>
                <tr class="child-row hidden" data-parent="1">
                    <td colspan="4">
                        <div class="child-content">
                            <p><strong>Capital:</strong> Sacramento</p>
                            <p><strong>Largest City:</strong> Los Angeles</p>
                            <p><strong>Area:</strong> 163,696 sq mi</p>
                            <p><strong>Statehood:</strong> September 9, 1850</p>
                        </div>
                    </td>
                </tr>

                <tr class="parent-row" data-id="2">
                    <td><span class="expand-btn">+</span></td>
                    <td>Texas</td>
                    <td>29.1M</td>
                    <td>South</td>
                </tr>
                <tr class="child-row hidden" data-parent="2">
                    <td colspan="4">
                        <div class="child-content">
                            <p><strong>Capital:</strong> Austin</p>
                            <p><strong>Largest City:</strong> Houston</p>
                            <p><strong>Area:</strong> 268,596 sq mi</p>
                            <p><strong>Statehood:</strong> December 29, 1845</p>
                        </div>
                    </td>
                </tr>

                <tr class="parent-row" data-id="3">
                    <td><span class="expand-btn">+</span></td>
                    <td>Florida</td>
                    <td>21.5M</td>
                    <td>South</td>
                </tr>
                <tr class="child-row hidden" data-parent="3">
                    <td colspan="4">
                        <div class="child-content">
                            <p><strong>Capital:</strong> Tallahassee</p>
                            <p><strong>Largest City:</strong> Jacksonville</p>
                            <p><strong>Area:</strong> 65,758 sq mi</p>
                            <p><strong>Statehood:</strong> March 3, 1845</p>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <script src="script.js"></script>
</body>
</html>

You can see the output in the screenshot below.

Create an Expandable Table with Collapsible Rows Using HTML, CSS, and JavaScript

Check out Create a Registration Form with HTML, CSS, and JavaScript

Step 2: Add CSS Styling

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f8f9fa;
    margin: 0;
    padding: 20px;
}

.container {
    max-width: 900px;
    margin: 0 auto;
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    color: #2c3e50;
    margin-bottom: 30px;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 20px;
}

th, td {
    padding: 12px 15px;
    text-align: left;
    border-bottom: 1px solid #ddd;
}

th {
    background-color: #f2f2f2;
    font-weight: bold;
}

tr:hover {
    background-color: #f5f5f5;
}

.expand-btn {
    display: inline-block;
    width: 20px;
    height: 20px;
    text-align: center;
    line-height: 20px;
    cursor: pointer;
    background-color: #e9ecef;
    border-radius: 50%;
    font-weight: bold;
}

.expand-btn.active {
    background-color: #007bff;
    color: white;
}

.child-row {
    background-color: #f8f9fa;
}

.child-content {
    padding: 15px;
    background-color: #f8f9fa;
}

.hidden {
    display: none;
}

You can see the output in the screenshot below.

Table with Collapsible Rows Using HTML, CSS, and JavaScript

Read Create Interactive HTML Forms with CSS and JavaScript

Step 3: Create the JavaScript Logic

document.addEventListener('DOMContentLoaded', function() {
    const expandButtons = document.querySelectorAll('.expand-btn');

    expandButtons.forEach(btn => {
        btn.addEventListener('click', function() {
            // Get the parent row
            const parentRow = this.closest('tr');
            const parentId = parentRow.getAttribute('data-id');

            // Find all child rows for this parent
            const childRows = document.querySelectorAll(`.child-row[data-parent="${parentId}"]`);

            // Toggle the expand/collapse button
            this.textContent = this.textContent === '+' ? '-' : '+';
            this.classList.toggle('active');

            // Toggle visibility of child rows
            childRows.forEach(row => {
                row.classList.toggle('hidden');
            });
        });
    });
});

You can see the output in the screenshot below.

Expandable Table with Collapsible Rows Using HTML, CSS, and JavaScript

This creates a basic expandable table with U.S. state data. When you click the + button, additional information about each state appears below the row.

Check out Call a JavaScript Function When a Checkbox is Checked or Unchecked

Method 2 – Dynamic Data Loading with JSON

For larger datasets, it’s more efficient to load the expanded content dynamically rather than having it all in the HTML. Here’s how to implement that:

Step 1: Update the HTML Structure

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Expandable Table</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h1>U.S. State Population Data</h1>
        <table id="expandableTable">
            <thead>
                <tr>
                    <th></th>
                    <th>State</th>
                    <th>Population</th>
                    <th>Region</th>
                </tr>
            </thead>
            <tbody id="tableBody">
                <!-- Table content will be loaded dynamically -->
            </tbody>
        </table>
    </div>
    <script src="script-dynamic.js"></script>
</body>
</html>

Read Check if a Checkbox is Checked Using jQuery

Step 2: Create a JSON Data File (states.json)

{
    "states": [
        {
            "id": 1,
            "name": "California",
            "population": "39.5M",
            "region": "West",
            "details": {
                "capital": "Sacramento",
                "largestCity": "Los Angeles",
                "area": "163,696 sq mi",
                "statehood": "September 9, 1850",
                "gdp": "$3.4 trillion",
                "industries": ["Technology", "Entertainment", "Agriculture", "Tourism"]
            }
        },
        {
            "id": 2,
            "name": "Texas",
            "population": "29.1M",
            "region": "South",
            "details": {
                "capital": "Austin",
                "largestCity": "Houston",
                "area": "268,596 sq mi",
                "statehood": "December 29, 1845",
                "gdp": "$1.9 trillion",
                "industries": ["Energy", "Technology", "Agriculture", "Manufacturing"]
            }
        },
        {
            "id": 3,
            "name": "Florida",
            "population": "21.5M",
            "region": "South",
            "details": {
                "capital": "Tallahassee",
                "largestCity": "Jacksonville",
                "area": "65,758 sq mi",
                "statehood": "March 3, 1845",
                "gdp": "$1.1 trillion",
                "industries": ["Tourism", "Agriculture", "International Trade", "Aerospace"]
            }
        },
        {
            "id": 4,
            "name": "New York",
            "population": "19.5M",
            "region": "Northeast",
            "details": {
                "capital": "Albany",
                "largestCity": "New York City",
                "area": "54,555 sq mi",
                "statehood": "July 26, 1788",
                "gdp": "$1.7 trillion",
                "industries": ["Finance", "Media", "Technology", "Healthcare"]
            }
        },
        {
            "id": 5,
            "name": "Illinois",
            "population": "12.7M",
            "region": "Midwest",
            "details": {
                "capital": "Springfield",
                "largestCity": "Chicago",
                "area": "57,914 sq mi",
                "statehood": "December 3, 1818",
                "gdp": "$875 billion",
                "industries": ["Manufacturing", "Finance", "Agriculture", "Transportation"]
            }
        }
    ]
}

Check out Handle Dropdown Change Event in jQuery

Step 3: JavaScript to Load and Display Data

document.addEventListener('DOMContentLoaded', function() {
    // Fetch the data
    fetch('states.json')
        .then(response => response.json())
        .then(data => {
            const tableBody = document.getElementById('tableBody');
            
            // Create rows for each state
            data.states.forEach(state => {
                // Create parent row
                const parentRow = document.createElement('tr');
                parentRow.className = 'parent-row';
                parentRow.setAttribute('data-id', state.id);
                
                parentRow.innerHTML = `
                    <td><span class="expand-btn" data-id="${state.id}">+</span></td>
                    <td>${state.name}</td>
                    <td>${state.population}</td>
                     <td>${state.region}</td>
                `;
                
                tableBody.appendChild(parentRow);
                
                // Create hidden child row (it will be populated when expanded)
                const childRow = document.createElement('tr');
                childRow.className = 'child-row hidden';
                childRow.setAttribute('data-parent', state.id);
                childRow.innerHTML = `<td colspan="4"><div class="child-content" id="content-${state.id}">Loading...</div></td>`;
                
                tableBody.appendChild(childRow);
            });
            
            // Add event listeners to expand buttons
            const expandButtons = document.querySelectorAll('.expand-btn');
            expandButtons.forEach(btn => {
                btn.addEventListener('click', function() {
                    const stateId = this.getAttribute('data-id');
                    const parentRow = this.closest('tr');
                    const childRow = document.querySelector(`.child-row[data-parent="${stateId}"]`);
                    const childContent = document.getElementById(`content-${stateId}`);
                    
                    // Toggle button appearance
                    this.textContent = this.textContent === '+' ? '-' : '+';
                    this.classList.toggle('active');
                    
                    // Toggle child row visibility
                    childRow.classList.toggle('hidden');
                    
                    // Load content if it's the first expansion
                    if (childContent.textContent === 'Loading...') {
                        // Find the state data
                        const stateData = data.states.find(state => state.id == stateId);
                        const details = stateData.details;
                        
                        // Create the content HTML
                        let contentHTML = `
                            <div class="details-grid">
                                <div class="detail-item">
                                    <strong>Capital:</strong> ${details.capital}
                                </div>
                                <div class="detail-item">
                                    <strong>Largest City:</strong> ${details.largestCity}
                                </div>
                                <div class="detail-item">
                                    <strong>Area:</strong> ${details.area}
                                </div>
                                <div class="detail-item">
                                    <strong>Statehood:</strong> ${details.statehood}
                                </div>
                                <div class="detail-item">
                                    <strong>GDP:</strong> ${details.gdp}
                                </div>
                                <div class="detail-item">
                                    <strong>Major Industries:</strong> ${details.industries.join(', ')}
                                </div>
                            </div>
                        `;
                        
                        childContent.innerHTML = contentHTML;
                    }
                });
            });
        })
        .catch(error => {
            console.error('Error loading data:', error);
            document.getElementById('tableBody').innerHTML = '<tr><td colspan="4">Error loading data. Please try again later.</td></tr>';
        });
});

Read Execute Functions After Page Load Using jQuery

Step 4: Add Additional CSS for the Dynamic Version

/* Add these styles to your existing CSS */
.details-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    grid-gap: 15px;
    padding: 10px;
}

.detail-item {
    background-color: white;
    padding: 10px;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.loading {
    text-align: center;
    padding: 20px;
    color: #666;
}