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.
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>.