Expandable Row Detail Table — Accordion Rows for Order/Record Line Items

Expandable Row Detail Table · Tables · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Detail rows linked to their summary row via explicit data attributes, not fragile DOM-adjacency assumptions
Real aria-expanded state on the summary row kept in sync with the detail row's visibility on every toggle
Native hidden attribute used for the collapsed state, not just a CSS display:none class
Delegated click handling on the table means dynamically added rows work without additional listener setup
Whole-row click reuses the same toggle button's click handler rather than duplicating the expand logic
Rotating chevron icon gives a clear, animated visual affordance for expanded/collapsed state
aria-label on the toggle button dynamically updates between "Expand" and "Collapse" wording
Full-width detail panel supports any content — line items, metadata, nested tables, or actions

About this UI Snippet

Expandable Row Detail Table — Inline Drill-Down Without a Modal

Screenshot of the Expandable Row Detail Table snippet rendered live

Showing every order's full line-item breakdown directly in a table would make it unreadable; hiding that detail behind a separate page or modal adds friction for a quick check. This pattern splits the difference: each row's detail is a second, adjacent table row that stays hidden until the user expands it, keeping the list scannable by default while making full detail one click away, in place, with no navigation.

Two rows per record, connected by a data attribute, not DOM adjacency alone

Each record is actually two <tr> elements — a .row-main summary row and a .row-detail row directly beneath it containing a single full-width <td colspan="5"> with the detail content inside. They're linked by matching data-row/data-detail-for values rather than assumed adjacency, so the toggle logic — table.querySelector(.row-detail[data-detail-for="${rowId}"]) — finds the correct detail row explicitly even if the table's structure changes, rather than relying on fragile "next sibling" DOM traversal that would break if anything were inserted between them.

aria-expanded on the row itself, not just a visual class

.row-main carries aria-expanded="true"/"false" directly, toggled in the same click handler that shows/hides the detail row — giving assistive technology accurate, real-time information about which rows are currently expanded, matching how a native disclosure widget should behave, rather than relying purely on a CSS class a screen reader has no way to interpret.

hidden attribute, not just CSS display, for the detail row

The collapsed detail row uses the native hidden boolean attribute rather than a CSS class toggling display: none — functionally similar for visual rendering, but hidden is the semantically correct HTML mechanism for "this content is not currently relevant," and it means the detail row is guaranteed to be excluded from things like browser find-in-page and the accessibility tree without any additional CSS needing to account for it.

Whole-row click as a convenience, not a replacement for the toggle button

Beyond the dedicated toggle button, clicking anywhere else on a .row-main row also triggers the same expand/collapse by simply calling .click() on that row's own toggle button — reusing the exact same code path rather than duplicating the expand logic, so there is only one real implementation of "toggle this row" that both interaction methods funnel through.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain why linking summary and detail rows via explicit data attributes is more robust than relying on the detail row always being the "next sibling" in the DOM, and what could break if that assumption were made instead. It's also worth asking for a version that only allows one row to be expanded at a time (accordion-exclusive behavior), or one that lazy-loads a row's detail content via a fetch call the first time it's expanded rather than rendering it all upfront.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an expandable-row data table in HTML, CSS and vanilla JavaScript where clicking a row reveals a full-width detail panel beneath it — no external libraries.

Requirements:
- A table where each data record is represented by two adjacent table rows: a compact summary row and a hidden detail row containing a single full-width cell with expanded content (e.g. line items and metadata).
- Link each summary row to its corresponding detail row using explicit matching data attributes (not by assuming DOM adjacency), so the toggle logic looks up the correct detail row by that identifier.
- Use the native hidden attribute (not a CSS display:none class) to control the detail row's collapsed state.
- Implement the expand/collapse toggle using a single delegated click listener on the table itself, so rows added to the table later work without needing additional listener setup.
- Keep the summary row's aria-expanded attribute and the toggle button's aria-label text in sync with the actual expanded/collapsed state at all times.
- Make the entire summary row clickable to expand/collapse it (not just a small toggle icon), while still keeping only one real implementation of the toggle logic that both interaction paths share.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Pair every summary row with a matching detail rowGive both rows the same identifier via data-row on the .row-main and data-detail-for on its .row-detail.
  2. 2
    Keep the detail row hidden by defaultAdd the hidden attribute (not a CSS class) to every .row-detail on initial render.
  3. 3
    Set colspan to match your column countThe detail row's single <td> needs colspan equal to the number of columns in your table header for the panel to span full width.
  4. 4
    Customize the detail panel content freelyAnything can go inside .detail-panel — line items, metadata, even a small nested table or action buttons.
  5. 5
    Add or remove rows dynamicallySince the click handler is delegated on the table itself, any new .row-main/.row-detail pair added later works immediately with no extra listener wiring.

Real-world uses

Common Use Cases

COMMERCE
Order Management Tables
Show order summaries in a compact list with full line-item breakdown available inline on demand.
ADMIN
Admin Record Lists
Any admin table listing records that have more detail than fits in a summary row (users, transactions, tickets).
FINANCE
Transaction / Invoice Tables
Expand a transaction row to show its line items, fees, and metadata without leaving the list view.
LOGS
Audit / Activity Log Tables
Keep a log summary compact while making full event detail available inline per entry.

Got questions?

Frequently Asked Questions

Each summary row has a data-row identifier and its matching detail row has a data-detail-for attribute with the same value; the click handler looks up the detail row explicitly via that attribute match rather than assuming it's always the very next sibling in the DOM.

hidden is the semantically correct native HTML mechanism for content that isn't currently relevant — it's automatically excluded from the accessibility tree and browser find-in-page without requiring any CSS rule, unlike a custom class that only has meaning because a stylesheet happens to define display:none for it.

Both — clicking anywhere on a .row-main row (except directly on the toggle button, which has its own handler) programmatically clicks that row's toggle button, so the whole row is a convenient click target while the actual expand/collapse logic exists in exactly one place.

No — the click listener is delegated on the table element itself using event bubbling and .closest(), so any new .row-main/.row-toggle pairs added to the table later work immediately without additional JavaScript wiring.

It must be set manually to match the number of columns in your table header — in this demo that's 5 (toggle, order, customer, total, status) — so the detail panel visually spans the table's full width rather than only one column.

Yes — the summary row carries a live aria-expanded attribute reflecting its actual current state, and the toggle button's aria-label text switches between "Expand" and "Collapse" wording, both updated in the same handler that shows or hides the detail row.