Home / Snippets / Layout /

Responsive table with container queries

A data table that collapses into a card-stack layout at narrow container widths using CSS container queries, keeping data readable at any size.

Name Role Status
Alice Chen Engineer Active
Ben Okafor Designer Active
Clara Liu Manager Away

Drag the bottom-right corner of the box to resize and watch the table collapse into cards.

New feature
layoutno-js

Quick implementation

.table-cq {
  container-type: inline-size;
}

.rwd-table {
  width: 100%;
  border-collapse: collapse;
}

.rwd-table th,
.rwd-table td {
  padding: 0.6rem 0.75rem;
  text-align: left;
  border-bottom: 1px solid oklch(0.28 0.03 265);
}

.rwd-table th {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

@container (max-width: 28rem) {
  .rwd-table thead {
    display: none;
  }

  .rwd-table tr {
    display: block;
    margin-bottom: 0.75rem;
    border: 1px solid oklch(0.28 0.03 265);
    border-radius: var(--radius);
    padding: 0.5rem;
  }

  .rwd-table td {
    display: flex;
    justify-content: space-between;
    border-bottom: none;
    padding: 0.35rem 0.5rem;
  }

  .rwd-table td::before {
    content: attr(data-label);
    font-size: 0.8rem;
    color: var(--muted);
    font-weight: 600;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building accessible responsive tables.

Goal: A data table that switches from standard tabular layout to a
card-stack layout at narrow container widths using CSS container queries,
with data-label attributes surfacing column headers as inline labels.

Technical constraints:
- Apply container-type: inline-size to a wrapper div around the table.
- Use @container (max-width: 28rem) to trigger the card layout.
- In card mode: hide <thead> with display: none, convert <tr> to
  display: block, convert <td> to display: flex with
  justify-content: space-between.
- Surface column labels via td::before { content: attr(data-label) }.
- Use oklch() for all color values — no hex or rgba.
- Do not use media queries — only @container queries.

Framework variant (pick one):
A) Vanilla CSS + HTML — add data-label attributes manually to each <td>.
B) React component — accepts a columns array and rows array; automatically
   adds data-label to each cell based on the column definition.

Edge cases to handle:
- If a cell has no data-label attribute, attr(data-label) returns an empty
  string — ensure the ::before pseudo-element collapses gracefully.
- Ensure <caption> remains visible in both modes for screen readers.
- When display: block is applied to <tr>, the inherent table semantics
  are lost for AT — consider adding role="row" and role="cell" in
  card mode if accessibility is critical.

Return CSS only (or a React component if variant B).

Why

Responsive tables have long been solved by viewport media queries, but a table in a sidebar or a narrow card panel needs to respond to its container, not the viewport. Container queries make this trivial — the same table component works correctly at any width context it is placed in.

The logic

container-type: inline-size on the table wrapper creates the query context. At container widths below 28rem, the <thead> is hidden and each <tr> is converted to a block card via display: block. Each <td> uses display: flex with justify-content: space-between to put the label on the left and the value on the right. The label text comes from data-label attributes on each <td>, surfaced via the content: attr(data-label) pseudo-element.

Accessibility & performance

When the table switches to card layout, <thead> is hidden but the data-label attributes on each cell preserve semantic association. Screen readers announcing display: block table cells will lose the inherent header association — consider role="rowheader" on label pseudo-elements or use an accessible description in the <caption>.