webdevpuneet.com

Table with Merged Row Groups (rowspan) — Free HTML CSS JS Snippet

Table with Merged Row Groups (rowspan) · Tables · Plain HTML, CSS & JS · Live preview

CategoryTables

Copy & paste

Source Code

<div class="wrap">
  <div class="table-head">
    <h2 class="table-title">Course Schedule by Department</h2>
    <span class="hint">Click a department cell to collapse its group</span>
  </div>
  <table class="tbl" id="tbl">
    <thead>
      <tr>
        <th>Department</th>
        <th>Course</th>
        <th>Instructor</th>
        <th class="num">Seats</th>
      </tr>
    </thead>
    <tbody id="tbody"></tbody>
  </table>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

True HTML rowspan merges the category column across each group, not a CSS-only visual approximation
rowspan value computed automatically from each group's row count, never hardcoded per group
Click-to-collapse toggle per group hides its rows while keeping the merged category cell visible
Rotating chevron communicates collapse state using the same convention as this library's accordion patterns
Row visibility toggled via a numeric group-index data attribute, avoiding fragile string-based selector matching
Per-row seat availability color-coding composes independently of the grouping and collapse mechanics
Course count shown inline in the merged department cell for quick scanning even while collapsed
Works with any number of groups or rows per group — all HTML is generated from the data structure

About this UI Snippet

Table with Merged Row Groups — rowspan Category Column & Click-to-Collapse

Screenshot of the Table with Merged Row Groups (rowspan) snippet rendered live

Repeating the same department or category name on every row of a grouped table wastes visual space and makes it harder to see where one group ends and the next begins — a merged cell that spans the whole group communicates both facts at once: what the group is, and exactly how many rows belong to it. This table builds that merged-cell layout directly with the HTML rowspan attribute, and layers a click-to-collapse toggle on top so any group can be tucked away without leaving the table.

rowspan computed from group length, not hardcoded

render() only emits a <td class="dept-cell" rowspan="..."> on the *first* row of each group (ri === 0), and its rowspan value is set directly from group.rows.length — so a department with three courses gets rowspan="3", a department with two gets rowspan="2", with no manual counting required anywhere. Every subsequent row in that group simply omits the department <td> entirely, letting the browser's native table layout algorithm handle the vertical merge — this is what rowspan does structurally, not a CSS visual trick layered on top of a normal grid of cells.

Collapse state lives on the DOM, driven by a data attribute match

Clicking a .dept-cell toggles its own .collapsed class and then queries every tr[data-group="N"].grouped-row sharing that group's index to hide or show them via a .hidden class (display: none). Every row — including the first row that holds the merged department cell — carries a data-group attribute, but only rows *after* the first get the additional .grouped-row class, since the first row (which holds the always-visible merged department cell) should never itself be hidden when its own group collapses — only the rows beneath it.

A rotating chevron indicates collapse state without extra text

The department cell's ▾ (down-caret) character is wrapped in a .chev span with a CSS transition: transform 0.2s; adding .collapsed to the parent .dept-cell rotates it -90deg to point sideways, the same expand/collapse convention used throughout this library's accordion and tree patterns, applied here inside a table cell instead of a standalone button.

Seat availability color-coded independently of the grouping

seatsHtml() renders each row's seat count as "Full" (red) at zero seats, a red "N left" warning at three or fewer, or a green "N open" otherwise — a status signal layered on top of, and entirely independent from, the department grouping and collapse mechanics. This demonstrates that the rowspan-merge pattern composes cleanly with other per-row styling; grouping the rows visually doesn't require flattening or simplifying what each individual row can show.

Why the group index, not the department name, drives the query

Hiding rows uses data-group="N" (a numeric array index) rather than matching on the department name string — avoiding any ambiguity if two groups happened to share a display name, and avoiding the need to escape a name value into a CSS attribute-selector string, which is a more fragile approach once department names can contain quotes or other characters that need selector-escaping.

Step by step

How to Use

  1. 1
    Click any department cellCollapses that department's course rows, leaving only the merged department cell visible with its course count.
  2. 2
    Click again to expandThe chevron rotates back and the group's rows reappear.
  3. 3
    Notice the seat availability colorsZero seats shows red "Full," three or fewer shows a red "N left" warning, and anything above shows a green "N open."
  4. 4
    Replace GROUPS with real dataUpdate the array with your own department/category names and per-row course, instructor, and seat data — rowspan values compute automatically from each group's row count.
  5. 5
    Adjust the seat-warning thresholdChange the "seats <= 3" condition in seatsHtml() to whatever count should trigger the warning styling.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a Tailwind CSS version.

Real-world uses

Common Use Cases

Course catalogs and academic scheduling tools
The core use case — group courses by department with a merged label column, letting students collapse departments they are not interested in.
Categorized product or inventory listings
Any table grouped by a category (product line, region, team) benefits from a merged category column instead of a repeated value on every row.
Grouped financial or reporting tables
Pair with the Grouped Rows Table pattern for reports organized by account category, cost center, or region.
Staff scheduling and shift assignment grids
Group shifts or assignments by team or location with a collapsed default view for teams not currently being reviewed.
Learn rowspan-based table grouping
A clean example of computing rowspan values from data length and correctly scoping a collapse toggle to only the rows that should hide, leaving the merged cell's own row always visible.

Got questions?

Frequently Asked Questions

render() sets the rowspan attribute on each group's first-row department cell directly to group.rows.length — the number of course rows in that group. A department with three courses automatically gets rowspan="3" with no manual counting or hardcoded values anywhere in the code.

The .grouped-row class is only applied to rows after the first row in each group (ri > 0). The first row holds the merged department cell via rowspan and must always stay visible even when its own group is collapsed, since that cell is what the user clicks to expand the group again — only the rows beneath it should ever be hidden.

Matching hidden rows against a numeric array index (data-group="2") avoids two problems a name-based approach would have: ambiguity if two groups ever shared the same display name, and the need to safely escape a name string for use inside a CSS attribute selector, which becomes fragile once names can contain quotes or special characters.

The department cell's .chev span has a CSS transition on transform. When the parent .dept-cell gains the .collapsed class (toggled on click), a rule rotates the chevron -90 degrees; removing the class rotates it back to its default downward-pointing orientation. This is the same rotating-indicator convention used by this library's accordion and tree-menu patterns.

No — the click handler reads only the clicked cell's own data-group value and queries rows matching that exact group index, so collapsing or expanding one department has no effect on any other department's rows or collapse state.

Add more objects to the GROUPS array (each with a dept name and a rows array), or add more row objects to an existing group's rows array. The rowspan value, row rendering, and collapse-toggle logic all derive from the array structure automatically, requiring no other code changes.