Snippets /

Responsive table

A data table that collapses into stacked cards on narrow containers using container queries.

NameRoleStatus
Alice Chen Engineer Active
Bob Rivera Designer Away
Carol Tanaka PM Active
Widely supported
layoutui

Quick implementation

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

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

.responsive-table th {
  text-align: left;
  padding: 0.6rem 0.75rem;
  font-weight: 600;
  color: oklch(0.65 0.02 260);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.responsive-table td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid oklch(0.25 0.01 260);
}

.responsive-table td::before {
  content: attr(data-label);
  display: none;
  font-weight: 600;
  color: oklch(0.65 0.02 260);
  font-size: 0.75rem;
  text-transform: uppercase;
}

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

  .responsive-table tr {
    display: block;
    padding: 0.75rem 0;
    border-bottom: 1px solid oklch(0.25 0.01 260);
  }

  .responsive-table td {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.3rem 0.75rem;
    border-bottom: none;
  }

  .responsive-table td::before {
    display: inline;
  }
}

Prompt this to your LLM

Paste this into ChatGPT, Claude, or any code-generating model to scaffold the pattern instantly.

Create a responsive data table using CSS container queries.
Wrap the table in a div with container-type: inline-size.
At full width, display a normal table with header row. When
the container is narrower than 32rem, hide the thead and
make each tr a block with each td displayed as flex with
space-between. Use td::before with content: attr(data-label)
to show the column header inline. Each td in the HTML needs
a data-label attribute matching its column header. Use
oklch() colors throughout.

Why this matters

Tables are notoriously difficult to make responsive. Horizontal scrolling is a poor experience, and simply shrinking column widths makes text unreadable. This pattern collapses each row into a mini-card where each cell is labeled, preserving all data in a mobile-friendly stack. Using container queries instead of media queries means the table adapts based on its parent's width, not the viewport, making it work inside sidebars and modals too.

The logic

The wrapper element is declared as an inline-size container with container-type: inline-size. Inside the @container query at max-width: 32rem, the <thead> is hidden with display: none. Each <tr> becomes display: block so it stacks vertically. Each <td> becomes display: flex with justify-content: space-between, creating a label-on-left, value-on-right layout. The ::before pseudo-element reads the data-label attribute to show the original column name, replacing the hidden header.

Accessibility & performance

The <table> element retains its semantic structure at all sizes; only the visual presentation changes. Screen readers still navigate by rows and cells. The data-label pseudo-element content is not exposed to all assistive technology, but the actual cell data in the DOM remains accessible. Use scope="col" on <th> elements for proper header association. Container queries are well-supported in modern browsers and have negligible performance impact since they only evaluate when the container resizes.