Data tables are widely used in web design to present organized and structured data. However, when they contain large amounts of data, it can be challenging to optimize for user experience.
When data tables are embedded within a responsive web design, the problem becomes even more complex. To ensure that tables are displayed correctly on all devices, developers often resort to hiding table columns on smaller screens. This can be a frustrating experience for users, who must resort to scrolling horizontally or zooming out to access the hidden data.
There are several basic strategies we can use to build responsive data tables. In this article, we’ll review some additional approaches we can take to further optimize responsive data table UX with CSS.
Jump ahead:
One of the major challenges associated with tables containing large amounts of data is user difficulty in maintaining visibility and context, particularly when scrolling horizontally or vertically. Fixed rows and columns within the table are beneficial for addressing this issue, helping to ensure that important information remains visible at all times.
For example, a fixed header and footer (i.e., the first and last rows of the table), provide users with continuous access to essential data points while scrolling through the rest of the table’s content. This approach provides a consistent reference point and prevents the loss of context, enabling users to analyze and interpret data more effectively.
We can implement fixed headers and footers using CSS styling and the position: sticky
property. When we assign this property to the table’s header and footer elements, we ensure their fixed position at the top and bottom of the table, respectively. This way, those rows remain visible even when the user is scrolling through extensive data sets:
//css .table-wrapper { max-height: 500px; overflow-y: scroll; } thead { position: sticky; top: 0; z-index: 2; } tfoot { position: sticky; bottom: 0; z-index: 2; }
Here, we set the .table-wrapper
class to have a maximum height of 500px
and the overflow-y
property to scroll
to enable vertical scrolling. We also set the position
property to sticky
for the thead
and tfoot
elements, the top
property to 0
for the thead
element, and the bottom
property to 0
for the tfoot
element. This ensures that the header and footer rows always remain fixed at the top and bottom of the table, regardless of scrolling:
We can also fix the first set of columns. This is helpful for columns that contain identifying data, like names or serial numbers. With fixed columns, users can maintain a reference point while scrolling horizontally through the remaining data:
//html <thead> <tr> <th>ID</th> <th class="fixed-column">Name</th> ... <!-- rest of the data --> </tr> </thead> <tbody> <tr> <td>1</td> <td class="fixed-column">John Doe</td> <td>[email protected]</td> ... <!-- rest of the data --> </tr> </tbody>
//css .fixed-column { position: sticky; left: 0; z-index: 1; } td.fixed-column { background-color: #fff; }
Here, we add the fixed-column
class name to the column holding the names. The position
property is set to sticky
for the fixed-column
class, and the left
property is set to 0
to ensure that the column remains fixed on the left side of the table.
The z-index
property is set to 1
to ensure that the column remains visible even when scrolling through the rest of the table’s content. We set the column’s background-color
property to black (# ffff
) to ensure it remains consistent with the rest of the table.
By fixing a particular set of columns and rows, we ensure that important information remains visible and accessible at all times, providing a consistent reference point, avoiding loss of context, and enabling users to analyze and interpret data more effectively:
See the Pen
having fixed column(s) and rows by Timonwa (@timonwa/)
on CodePen.
The stacking approach is a technique commonly used in responsive web design to enhance the readability and usability of tables on smaller screens. It involves transforming the rows of a table into vertically stacked blocks or cards, making it easier for users to scroll through and consume tabular data on mobile devices.
We can utilize CSS to modify the table’s layout and presentation on smaller screens to implement the stacking approach. Here’s an example implementation using CSS:
//html <table> ... <tbody> <tr> <td name="S/N">1</td> <td name="Name">John Doe</td> <td name="Email">[email protected]</td> <td name="Phone">123-456-7890</td> <td name="Address">123 Main St</td> <td name="City">New York</td> <td name="Age">35</td> <td name="Occupation">Software Engineer</td> <td name="Company">ABC Corporation</td> <td name="Salary">$80,000</td> <td name="Department">Engineering</td> <td name="Join Date">2022-01-01</td> <td name="Notes" >Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td > </tr> <!-- rest of the table --> </tbody> ... </table>
//css @media screen and (max-width: 768px) { table { width: 300px; } thead { display: none; } tr { display: block; margin-bottom: 20px; border-radius: 5px; border: 1px solid #ddd; } tr td { display: block; width: 100%; position: relative; white-space: pre-wrap; border: none; border-bottom: 1px solid #ddd; } tr td:first-child { display: none; } tr td:nth-child(2) { background-color: #4caf50; color: white; padding: 8px; border-radius: 5px 5px 0 0; } tr td:not(:nth-child(2))::before { content: attr(name); position: absolute; top: 8px; left: 0; font-weight: bold; text-align: left; width: 100%; padding: 0 8px; } }
In the HTML code, we add a name
attribute to each <td>
element. This attribute is used to display the labels for the data blocks. In the CSS code, we use a media query to target screens with a maximum width of 768px
. Inside the media query, we apply the following CSS rules:
table
: The table has a width of 300px
to ensure it fits well on smaller screensthead
: The table header is hidden as it’s not necessary for the stacking approachtr
: Each table row is displayed as a block element with a bottom margin of 20px
to create spacing between the blocks. The rows also have a 1px
solid border for visual separation. The border-radius
property is used to create rounded corners for the top edges of the blockstr td
: Each table cell is displayed as a block element with a width of 100%
, causing the cells to stack vertically. position: relative
is used to position the label text with position: absolute
tr td:first-child
: The first cell in each row (in this example, “Serial Number”) is hidden using display: none
since it’s not required in the stacked layouttr td:nth-child(2)
: The second cell in each row (in this example, “Name”) has a green background with white text. It has a top border radius of 5px
to create a rounded top edgetr td:not(:nth-child(2))::before
: For cells other than the second cell in each row, a ::before
pseudo-element is used to display the label text (the name
attribute) as a bold header above the cell content. It’s positioned absolutely at the top left corner of the cellApplying this CSS code transforms the table into a vertically stacked layout on smaller screens, allowing users to more easily scroll through the tabular data. The stacking approach improves the table’s accessibility and UX on mobile devices:
See the Pen
The stacking approach (rows to blocks) by Timonwa (@timonwa/)
on CodePen.
We can take the stacking approach a step further by turning each block into an accordion. This allows users to expand and collapse the blocks to view the data they need. This is especially useful when handling large amounts of data, as it allows users to focus on specific information without having to scroll through the entire table. This can be achieved using JavaScript and more CSS:
//html <table> ... <tbody> <tr class="active"> <td name="S/N">1</td> <td name="Name">John Doe</td> <td name="Email">[email protected]</td> <td name="Phone" class="accordion-content">123-456-7890</td> <td name="Address" class="accordion-content">123 Main St</td> <td name="City" class="accordion-content">New York</td> <td name="Age" class="accordion-content">35</td> <td name="Occupation">Software Engineer</td> <td name="Company" class="accordion-content">ABC Corporation</td> <td name="Salary" class="accordion-content">$80,000</td> <td name="Department" class="accordion-content">Engineering</td> <td name="Join Date" class="accordion-content">2022-01-01</td> <td name="Notes" class="accordion-content" >Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td > </tr> <tr> <td name="S/N">1</td> <td name="Name" class="fixed-column">John Doe</td> <td name="Email">[email protected]</td> <td name="Phone" class="accordion-content">123-456-7890</td> ... <!-- rest of the table --> </tr></tbody > ... </table>
//css tr { cursor: pointer; } tr .accordion-content { display: none; } tr.active .accordion-content { display: block; }
//javascript document.addEventListener("DOMContentLoaded", function () { var accordionRows = document.querySelectorAll("tr"); accordionRows.forEach(function (row) { row.addEventListener("click", function () { this.classList.toggle("active"); }); }); });
Here’s how our sample table looks using the stacking approach with accordions:
See the Pen
The stacking approach plus accordion by Timonwa (@timonwa/)
on CodePen.
In the code snippets above, we add the accordion-content
class to the cells that we want to hide. We also add the active
class to the row we want to expand by default.
In the CSS code, we add some styles to hide and display the content when the row is active. In the JavaScript code, we add an event listener to toggle the active
class when the row is clicked.
To optimize tables for smaller screens and enhance their responsiveness, we can selectively hide columns based on the screen size and toggle their visibility using JavaScript. Hiding specific columns is beneficial for various reasons. For instance, it can prevent horizontal scrolling on smaller screens by hiding columns with extensive text or large amounts of data.
This approach ensures that the table fits within the available screen width and provides a seamless user experience. Additionally, we can hide columns that contain nonessential information, or elements like buttons or icons, reducing visual noise on smaller screens.
To implement this technique effectively, we can utilize CSS to hide the desired columns on smaller screens and then leverage JavaScript to toggle the visibility of these columns based on the screen size.
The following example demonstrates an implementation of this approach using CSS and JavaScript:
//html <div class="checkboxes"> <label for="togglePhone"> <input type="checkbox" id="togglePhone" />Phone </label> <label for="toggleAddress"> <input type="checkbox" id="toggleAddress" />Address </label> <label for="toggleCity"> <input type="checkbox" id="toggleCity" />City </label> <label for="toggleAge"> <input type="checkbox" id="toggleAge" />Age </label> <label for="toggleOccupation"> <input type="checkbox" id="toggleOccupation" />Occupation </label> <label for="toggleCompany"> <input type="checkbox" id="toggleCompany" />Company </label> <label for="toggleSalary"> <input type="checkbox" id="toggleSalary" />Salary </label> <label for="toggleDepartment"> <input type="checkbox" id="toggleDepartment" />Department </label> <label for="toggleJoin-date"> <input type="checkbox" id="toggleJoin-date" />Join Date </label> <label for="toggleNotes"> <input type="checkbox" id="toggleNotes" />Notes </label> </div> <div class="table-wrapper"> <table> <thead> <tr> <th>ID</th> <th>Name</th> <th>Email</th> <th class="phone">Phone</th> <th class="address">Address</th> <th class="city">City</th> <th class="age">Age</th> <th class="occupation">Occupation</th> <th class="company">Company</th> <th class="salary">Salary</th> <th class="department">Department</th> <th class="join-date">Join Date</th> <th class="notes">Notes</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>John Doe</td> <td>[email protected]</td> <td class="phone">123-456-7890</td> <td class="address">123 Main St</td> <td class="city">New York</td> <td class="age">35</td> <td class="occupation">Software Engineer</td> <td class="company">ABC Corporation</td> <td class="salary">$80,000</td> <td class="department">Engineering</td> <td class="join-date">2022-01-01</td> <td class="notes" >Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td > </tr> ... <!-- rest of the table --> </tbody> </table> </div>
//css .checkboxes { display: none; justify-content: flex-start; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 20px; } @media screen and (max-width: 768px) { .checkboxes { display: flex; } th.phone, td.phone, th.address, td.address, th.city, td.city, th.age, td.age, th.occupation, td.occupation, th.company, td.company, th.salary, td.salary, th.department, td.department, th.join-date, td.join-date, th.notes, td.notes { display: none; } }
//javascript const checkboxes = document.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach((checkbox) => { checkbox.addEventListener("change", (event) => { const columnClass = event.target.id.replace("toggle", "").toLowerCase(); const columnCells = document.querySelectorAll(`td.${columnClass}`); const columnHeads = document.querySelectorAll(`th.${columnClass}`); if (event.currentTarget.checked) { columnCells.forEach((cell) => { cell.style.display = "table-cell"; }); columnHeads.forEach((cell) => { cell.style.display = "table-cell"; }); } else { columnCells.forEach((cell) => { cell.style.display = "none"; }); columnHeads.forEach((cell) => { cell.style.display = "none"; }); } }); });
We create the checkboxes and a class in the HTML code for each column that we want to hide. In the CSS, we add some styles to hide the checkboxes on larger screens and display them on smaller screens. We also add some styles to hide the columns on smaller screens.
In the JavaScript code, we select all the checkboxes and add an event listener to each checkbox. When a checkbox is checked, we get its id
and save it to the columnClass
variable.
We also select and display all cells and headers that have a class name equal to the columnClass
variable. When a checkbox is unchecked, we select and hide all cells and headers that have a class name equal to the columnClass
variable from the checkbox id
.
Here’s our approach for selectively hiding and toggling column visibility in action:
See the Pen
Selectively hiding and toggling column visibility by Timonwa (@timonwa/)
on CodePen.
The technique of using collapsible rows is an alternative approach for optimizing tables for smaller screens. Rather than stacking rows or hiding columns, this method focuses on collapsing rows to save space and provide a more condensed view of the table. It involves displaying a summary row for each data entry, containing essential information in a condensed format.
By initially presenting only the summarized information, space is conserved on smaller screens. Users can then expand a specific row to reveal its full details using an expand/collapse button, enabling access to more comprehensive data when needed.
Collapsing rows reduces the vertical space occupied by the table on smaller screens, making it easier for users to scroll through and find the desired information. This approach is particularly useful for tables with a large number of rows as they allow users to focus initially on the essential details and selectively explore additional information as required.
Implementing collapsible rows typically involves using JavaScript or CSS to toggle the visibility or height of the expanded content when the expand/collapse button is clicked. This dynamic adjustment of the table’s display based on user interactions provides a responsive and space-efficient layout for smaller screens.
Whereas the stacking approach rearranges the table’s content vertically, displaying one row per screen, the collapsible rows approach condenses the table vertically by presenting summary rows and allowing users to expand individual rows for more detailed information. Both techniques aim to enhance the usability of tables on smaller screens, but they differ in their presentation and interaction patterns.
Here’s an example implementation of collapsible rows using JavaScript and CSS:
//html <table> <thead> <tr> <th>Name</th> <th>Age</th> <th>Occupation</th> </tr> </thead> <tbody> <tr> <td> John Doe <button class="expand-btn" onclick="toggleDetails(this)"></button> </td> <td>35</td> <td>Software Engineer</td> </tr> <tr class="details"> <td colspan="3"> <p>Email: [email protected]</p> <p>Phone: 123-456-7890</p> <p>Address: 123 Main St</p> <p>City: New York</p> <!-- Additional details can be included here --> </td> </tr> <tr> <td> Jane Smith <button class="expand-btn" onclick="toggleDetails(this)"></button> </td> <td>42</td> <td>Marketing Manager</td> </tr> <tr class="details"> <td colspan="3"> <p>Email: [email protected]</p> <p>Phone: 987-654-3210</p> <p>Address: 456 Park Ave</p> <!-- Additional details can be included here --> </td> </tr> <!-- Add more rows as needed --> </tbody> </table>
//css .table-wrapper { width: 100%; max-width: 500px; overflow-x: auto; } table { width: 100%; border-collapse: collapse; } .details { display: none; } .expand-btn::after { content: "+"; margin: 5px; } .collapse-btn::after { content: "-"; margin: 5px; }
//javascript function toggleDetails(button) { const detailsRow = button.parentNode.parentNode.nextElementSibling; detailsRow.classList.toggle("details"); button.classList.toggle("expand-btn"); button.classList.toggle("collapse-btn"); }
Here’s the resulting table with collapsible rows:
See the Pen
Collapsing rows by Timonwa (@timonwa/)
on CodePen.
Grouping related columns and displaying them within a single cell or expanding panel reduces the table’s width and simplifies the display, making it more manageable on smaller screens while maintaining the organization and structure of the data.
Let’s look at an example to illustrate this technique. Let’s say we have a table displaying customer orders, with columns for Order ID, Customer Name, Email, Phone, Shipping Address, Product, Price, Quantity, Order Date, Order Status, Additional Notes, and a link to the product page.
Initially, on large screens, the data is displayed in separate columns, but on smaller screens, we can group the customer-related information and the product-related information into a single cell. This reduces the table’s width and simplifies the display, making it more manageable on smaller screens while maintaining the organization and structure of the data.
By employing data grouping, we simplify the display of tables on smaller screens by reducing their width and condensing related columns. Users can expand the groups as needed to view additional information, allowing for a more compact and organized representation of the data:
See the Pen
Data grouping by Timonwa (@timonwa/)
on CodePen.
Data prioritization is a concept that can be applied to any of the previously mentioned approaches. Identifying the most important data points and prioritizing them is an effective way to make a table more responsive.
It’s important to consider the context and purpose of the table when deciding which data points to prioritize. By prioritizing the most important data points, we can ensure that they are always visible and accessible to users, regardless of the screen size or device they are using. This helps maintain the table’s usability and readability on smaller screens, ensuring that users can easily access the information they need.
It’s critical to evaluate the specific requirements and priorities of the data being presented before deciding which data points to prioritize. It is also important to understand that all these techniques can be combined to create an even more responsive table. For example, we can use the stacking approach to rearrange the table’s content vertically and then use data grouping to combine related columns into a single cell.
Improving the user experience of responsive data tables enables u9. In this article, we reviewed several strategies for improving responsive data table UX: freezing or fixing rows or columns, implementing a stacking approach (both with and without accordions), selectively hiding and toggling column visibility (with an option to collapse rows), data grouping, and data prioritization.
You can build responsive data tables in CSS and then adopt some or all of the approaches shared in this article to further improve user experience. You can view and play with all the code snippets from this article on CodePen.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.