Table Row Detail Panel — Master-Detail List with Instant Inspector (JS)

Table Row Detail Panel (Master-Detail) · Tables · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

True master-detail layout
A fixed-size list beside a panel that updates in place, not an expanding row.
Instant panel updates
Clicking any row immediately re-renders the detail panel from that row's data.
Single source of truth
Both the list highlight and panel content derive from one selectedId lookup.
Genuine empty state
A dedicated prompt appears before any row is selected, not a default-open first row.
Real read-state mutation
Opening a message clears its unread flag in the actual data, not just visually.
Compact list, rich detail
The list truncates to essentials; the panel shows full content only once selected.
Selection highlight
The open row stays visually marked with an accent bar and tint.
Data-driven & no library
Renders from a MESSAGES array of objects — zero dependencies.

About this UI Snippet

Table Row Detail Panel — A Master List with an Always-Visible Inspector

Screenshot of the Table Row Detail Panel (Master-Detail) snippet rendered live

An email client's message list and reading pane is a specific, well-understood pattern: a compact list on one side, and a detail panel that updates instantly as you click different rows on the other — never expanding inline, never navigating away. This snippet builds that master-detail layout for a table in plain HTML, CSS, and vanilla JavaScript, distinct from an accordion-style expandable row that pushes content into the row itself.

Master-detail, not expand-in-place

An expandable table grows the clicked row taller to reveal its detail inline, pushing every row below it down the page. This pattern does the opposite: the list stays a fixed, compact grid, and a *separate* panel beside it shows the selected row's extended information. Clicking a different row doesn't grow anything — it swaps the panel's content instantly, exactly like clicking between emails in an inbox never resizes the message list.

One click, one source of truth

showDetail(id) looks up the clicked message by id, sets it as selectedId, and rebuilds both the list (to show the selection highlight and clear the unread dot) and the detail panel's content from that single message object. Because both halves of the UI render from the same lookup, the highlighted row in the list and the content in the panel can never disagree about which item is "current."

A real empty state before any selection

Before anything is clicked, the detail panel shows a dedicated empty state — an icon and "Select a row to see its details here" — rather than either a blank panel or a default-selected first row. This matches how a reading pane actually behaves the first time you open a mail client: there's a genuine prompt, not an implicit selection the user didn't make.

Read-state side effect, like a real inbox

Opening a message clears its unread flag and removes its bold styling and dot indicator on the very next render — a small but realistic detail that shows the detail panel isn't just a display surface, it's wired to actually mutate the underlying data the way clicking into an email marks it read.

Compact list columns, expanded detail content

The list intentionally shows only three columns (From, Subject, Date) with text-overflow: ellipsis truncation, while the detail panel shows the full sender email, full body text, and tags — information that would be far too dense to fit in the table itself. This division of "just enough to scan" versus "everything once selected" is the actual reason the master-detail pattern exists, as opposed to just making the table wider. Pair it with a selectable table if you also need multi-row bulk actions alongside single-row inspection, or a table row context menu for right-click actions on the same list.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than reverse-engineering a mail-client-style layout, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the list and the detail panel are both driven from the same selectedId lookup instead of tracking separate "highlighted row" and "shown detail" state, and why the unread flag is mutated on the real MESSAGES array rather than just toggled visually with a CSS class. The same assistant can help extend it — ask it to add keyboard navigation (up/down arrows to move the selection, Enter to open), support marking a message unread again from the detail panel, or add a responsive mode where the detail panel becomes a full-screen overlay on narrow viewports instead of a side-by-side column. Treat the code less like a finished artifact and more like a starting point for a conversation.

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 a master-detail list-with-inspector layout in plain HTML, CSS, and JavaScript, modeled on an email client's message list and reading pane — no libraries.

Requirements:
- A two-column layout: a compact list/table on one side (showing only a few essential columns, truncated with ellipsis if needed) and a separate detail panel on the other side, laid out side by side (not one expanding into the other, and not navigating to a different page).
- Clicking any row in the list must instantly update the detail panel's content to show that row's extended information (fields not visible in the compact list view), without resizing, reordering, or reflowing the list itself.
- Both the list's "currently selected" visual highlight and the detail panel's displayed content must be derived from the same single piece of state (e.g. a selected id) and the same lookup into the underlying data array, so they can never disagree about which record is currently shown.
- Before any row has been clicked, the detail panel must show a distinct, deliberate empty state (an icon plus a prompt like "Select a row to see its details") — not a blank panel and not a row selected by default.
- Include at least one example of the detail panel's selection causing a genuine mutation of the underlying data (for instance, marking a message as read when opened, clearing an unread indicator in the real data array, not just a CSS toggle), demonstrating the panel is wired to real state rather than being a static display-only surface.
- Visually distinguish unread/flagged rows in the list (e.g. bold text and a dot indicator) and confirm that indicator clears correctly, driven by the real data change, when that row is opened.

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
    Paste HTML, CSS, and JSAn inbox-style list renders on the left with an empty detail panel on the right.
  2. 2
    Click a rowThe detail panel instantly fills with that message's full sender, body, and tags.
  3. 3
    Click a different rowThe panel content swaps immediately — the list itself never grows or shifts.
  4. 4
    Note the unread dotUnread rows are bold with a dot; opening one clears both, mirroring a real inbox.
  5. 5
    Check the selection highlightThe currently open row stays visually marked in the list.
  6. 6
    Swap in your dataReplace MESSAGES with your own records; showDetail() is generic over any object shape.

Real-world uses

Common Use Cases

Inbox and messaging UIs
The canonical master-detail pattern — pair with a table search highlight box to filter the list.
Support ticket queues
Show full ticket context in a panel beside a compact queue list.
CRM contact browsers
Click a contact row to inspect full details without navigating away.
Admin record inspectors
Pair with a selectable table for list-plus-inspect-plus-bulk-action workflows.
Log and event viewers
Click an event row to see its full payload in a side panel.
Learning list-detail patterns
A reference for master-detail state — compare with an expandable table's inline accordion approach.

Got questions?

Frequently Asked Questions

An expandable row grows taller in place to reveal its detail, pushing every row beneath it down the page — the list's layout changes with every click. This pattern keeps the list a fixed grid and shows detail in a completely separate panel beside it, so clicking different rows never resizes or reflows the list itself — exactly like an email client's message list and reading pane.

Both. showDetail() looks up the clicked message by id and both re-renders the panel from it and sets that message's real unread property to false in the underlying MESSAGES array — so the bold styling and dot indicator that reflect "unread" are driven by an actual data mutation, the same way opening an email in a real inbox marks it read in the underlying mail store, not just visually.

The detail panel shows a dedicated empty state with an icon and a "Select a row to see its details here" message — there's no default-selected first row and no blank panel. This matches the real first-load behavior of a mail client's reading pane, which prompts for a selection rather than guessing one.

No — both are rendered from the same selectedId and the same MESSAGES.find() lookup inside showDetail(), so the highlighted row in the list and the content in the panel always refer to the same record. There's no separate state for "what's highlighted" versus "what's displayed."

Keep selectedId in component state, derive the selected record with a find() over your data array, and render the list (with a selected class check) and the detail panel (or its empty state) both from that same state and derived value — the click handler just needs to set the id, and both halves re-render consistently.