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;
}

This method improves the performance by loading detailed data only when needed, which is particularly useful for large datasets.

Check out Check Which Radio Button is Selected Using jQuery

Method 3 – Use jQuery for Simplicity

If you’re already using jQuery in your project, here’s an even simpler implementation:

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>jQuery Expandable Table</title>
    <link rel="stylesheet" href="styles.css">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <div class="container">
        <h1>U.S. Company Financial Data</h1>
        <table id="expandableTable">
            <thead>
                <tr>
                    <th></th>
                    <th>Company</th>
                    <th>Industry</th>
                    <th>Revenue (2024)</th>
                </tr>
            </thead>
            <tbody>
                <tr class="parent-row">
                    <td><span class="expand-btn">+</span></td>
                    <td>Apple Inc.</td>
                    <td>Technology</td>
                    <td>$394.3B</td>
                </tr>
                <tr class="child-row">
                    <td colspan="4">
                        <div class="child-content">
                            <h3>Company Details</h3>
                            <p><strong>Headquarters:</strong> Cupertino, California</p>
                            <p><strong>CEO:</strong> Tim Cook</p>
                            <p><strong>Founded:</strong> April 1, 1976</p>
                            <p><strong>Employees:</strong> 164,000+</p>
                            <p><strong>Products:</strong> iPhone, Mac, iPad, Apple Watch, Apple TV, AirPods</p>
                        </div>
                    </td>
                </tr>
                
                <tr class="parent-row">
                    <td><span class="expand-btn">+</span></td>
                    <td>Microsoft Corporation</td>
                    <td>Technology</td>
                    <td>$211.9B</td>
                </tr>
                <tr class="child-row">
                    <td colspan="4">
                        <div class="child-content">
                            <h3>Company Details</h3>
                            <p><strong>Headquarters:</strong> Redmond, Washington</p>
                            <p><strong>CEO:</strong> Satya Nadella</p>
                            <p><strong>Founded:</strong> April 4, 1975</p>
                            <p><strong>Employees:</strong> 181,000+</p>
                            <p><strong>Products:</strong> Windows, Office, Azure, Xbox, Surface</p>
                        </div>
                    </td>
                </tr>
                
                <tr class="parent-row">
                    <td><span class="expand-btn">+</span></td>
                    <td>Amazon.com Inc.</td>
                    <td>E-commerce, Cloud</td>
                    <td>$513.9B</td>
                </tr>
                <tr class="child-row">
                    <td colspan="4">
                        <div class="child-content">
                            <h3>Company Details</h3>
                            <p><strong>Headquarters:</strong> Seattle, Washington</p>
                            <p><strong>CEO:</strong> Andy Jassy</p>
                            <p><strong>Founded:</strong> July 5, 1994</p>
                            <p><strong>Employees:</strong> 1,540,000+</p>
                            <p><strong>Products/Services:</strong> E-commerce, AWS, Prime Video, Alexa, Kindle</p>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <script src="jquery-script.js"></script>
</body>
</html>

Step 2: jQuery Script

$(document).ready(function() {
    // Hide all child rows initially
    $('.child-row').hide();
    
    // Click handler for the expand buttons
    $('.expand-btn').click(function() {
        // Toggle the button text
        $(this).text($(this).text() === '+' ? '-' : '+');
        $(this).toggleClass('active');
        
        // Find and toggle the next child row
        $(this).closest('tr').next('.child-row').toggle('fast');
    });
});

This jQuery version is much more concise while still providing the same functionality.

Expandable tables represent just one technique in the broader toolkit of interactive data visualization. The best approach depends on your specific use case, audience needs, and technical constraints.

By mastering these techniques, you’ll be able to present large, complex datasets in a way that’s both manageable and intuitive, allowing users to find exactly what they need without drowning in information they don’t

I hope this guide has given you the tools you need to create more user-friendly data presentations in your web applications. Happy coding!

Other tutorials you may read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.