Experimenting with tabular data and container queries
Responsive tabular data has tormented many web developers for many decades. How can we provide our users with semantical markup whilst making sure the visual layout adapts itself to the size available? Container Queries give us the power to create an out-of-the-box solution.
Can we harness it?
CSS frameworks like eg. Bootstrap took the route of providing a small viewport solution in which tables are horizontally scrollable. Chris Coyier wrote a round-up of different approaches to this problem. None of these approaches are perfect. They all have their own UX drawbacks. You either can’t see every piece of information in one glance, you lose track of the column or row you want to focus on whilst scrolling both horizontally or vertically, or you have a lot of repeating information of the table header on each row.
I wanted to find out if I could come up with a solution that is visually more elegant.
Goals
- Use semantic markup
- ’Small viewport’- friendly layout
- ’Large viewport’- friendly layout
- Bonus: The user can switch between layouts
Design
We want to visually represent the data of multiple users.
In this case, for small viewports, a list of cards seems the way to go. The information is presented in a way that is easy to scan. The full name is a bit larger, the date of birth is a bit smaller, and the layout is more spread out evenly over a few columns. This allows parts of the information to be more emphasized. The constraint of preventing a horizontal scroll allows better focus on a single user.
For large viewports, let’s go with a table. We have sufficient screen real estate to show all the information per user in one single row. At the top of the table, we have the column headers. Screen readers will add the header text when reading out the table cells.
Experiments
In true CSS Zen Garden fashion, I try to use the same markup. As the large viewport layout is a table, we’ll go with a <table>
element. Using a table will give users of screen readers the bonus of the text of the column headers when reading out the table cells, also on small viewports, where the table headers are visually hidden.
Experiment 1: Media queries + HTML table
First, let’s try to use media queries to switch between the two layouts. At the breakpoint of 768px
, we switch from a list of cards to a table. As our user agent automatically provides table
elements with default styling and the goal of the large viewport is to show a table, we’ll use media queries to override and add styling for small viewports.
Let me try to share the point of this experiment with a very basic example containing the pieces of CSS to make my point clear.
/* Shared styles */
/* ... */
/* Small viewport styles */
@media (max-inline-size: 749px) {
tbody {
/* Display HTML table as grid */
display: grid;
/* ... */
}
/* ... */
}
/* Large viewport styles */
@media (min-inline-size: 750px) {
tr:nth-child(odd) {
background-color: lightgrey;
/* ... */
}
/* ... */
}
Pretty straightforward. We have a shared set of styles that are applied to both layouts. Then we have a set of styles that are applied to the small viewport layout and a set of styles that are applied to the large viewport layout.
Check out this CodePen where I added additional styling to make it look a bit nicer. Use the CodePen zoom feature to activate the layout switch, or visit the Full Page View and resize your browser viewport.
Experiment 2: Size container queries + HTML table
Media queries are great, but now we have container queries. This means we can encapsulate this responsive styling within the user list component itself and reuse it in other contexts, which are perhaps not the full page width. A quick wrap of the table to set containment on and a change of @media
to @container
and we’re good to go.
/* Shared styles */
.user-datalist-wrapper {
container-type: inline-size;
}
/* ... */
/* Small container styles */
@container (max-inline-size: 749px) {
tbody {
/* Display HTML table as grid */
display: grid;
/* ... */
}
/* ... */
}
/* Large container styles */
@container (min-inline-size: 750px) {
tr:nth-child(odd) {
background-color: lightgrey;
/* ... */
}
/* ... */
}
Experiment 3: Style container queries + HTML table
Style container queries kind of introduce a way to write conditionals in CSS. By setting a CSS custom property and querying the value of the custom property on the container, we could alter styling and switch between layouts, at runtime, using (mostly) CSS!
You might be thinking: ‘But we can already do that by adding a class to the container and using that class to switch between layouts.’ Yes, you’re right. But with the style container query approach, we are not increasing specificity, which makes your subsequent styling less complex.
Let’s rework our previous example. Instead of querying for the inline-size
of the container, we’ll query for the value of a CSS custom property named --user-datalist-view
to either the value grid
or table
.
/* Shared styles */
.user-datalist-wrapper {
--user-datalist-view: grid; /* Allowed values: grid, table */
}
/* ... */
/* Grid container styles */
@container style(--user-datalist-view: grid) {
tbody {
/* Display HTML table as grid */
display: grid;
/* ... */
}
/* ... */
}
/* Table styles */
@container style(--user-datalist-view: table) {
tr:nth-child(odd) {
background-color: lightgrey;
/* ... */
}
/* ... */
}
// Set the custom property to an allowed value to switch to the layout
document.querySelector('.user-datalist-wrapper').style.setProperty('--user-datalist-view', 'table');
From a CSS perspective, it seems like the most flexible approach. We can switch between layouts with a single CSS custom property. If we want to change the layout based on either the viewport or the container inline-size, we just have to change the value of the custom property in either a media or container query rule.
But should we?
Accessibility
As web artisans, we have to keep in mind that our projects are accessibility for all. With these experiments, I found two accessibility concerns.
Document sequence
Rachel Andrew shared the potential accessibility issues when we are re-ordering the visual layout sequence from the document sequence, at her talk at axe-con 2021.
WCAG Success Criterion 1.3.2 tells us that re-ordering the presentation of the content should maintain a meaningful sequence.
Although I tried to keep the example’s visual sequence shift as minimal as possible, do be aware that overly using this technique could lead to potential accessibility issues.
Browsers break a11y via Display Properties
A few years ago, depending on when you read this, Adrian Roselli discovered that several browsers break accessibility via Display Properties. As our previous experiments are actively changing the default table-related display properties to something else, we are potentially causing issues for users visiting using a screen reader.
Conclusion
Although the power of container queries allow us to build in an automatic visual layout changes to tables, based on the given space that they have available, or just by setting a single custom property, the fact that we cannot guarantee an accessible solution makes it that we just cannot have our cake and eat it, yet, I hope.
If you have any feedback, feel free to reach out via Mastodon.
✌️
Edits
Sat. 10th of June 2023- Renamed article from Thoughts on switching layouts with container style queries to Experimenting with tabular data and container queries to better fit the content.
- Refined the intro and added a conclusion.
- Added Adrian Roselli’s article It’s Mid-2022 and Browsers (Mostly Safari) Still Break Accessibility via Display Properties to the Accessibility section.