Scrollable Table with fixed Header using CSS

Here's one a lot of you will have encountered and that has been the cause for some major headaches. The case? So, you have a list of data that you present in a table. But there are quite a lot of rows, so the user will have to scroll the page. But there is more on the page so the table should only take a specific height. Hence the rows should be scrollable but with a fixed header. Sounds familiar? Given you some headaches too? Well, here is a different approach with is easier and will give you the desired effect in no time!

A table with a fixed header

Now if you Google for this, you will a lot of solutions for this. But most of them use a fixed width for the table. So say goodbye to the great responsive design you had in mind. Well, you could use percentages of course. But then the scrollbar kicks in, making the total width of the rows a bit smaller than the total width of the header. Causing a visible shift in the layout, especially if you have a lot of columns. Where the column header is not exactly right above the column itself. This can also be solved by adding some Javascript to make the adjustments to compensate the scrollbar width. So after a lot of precision guess work, dozens of lines of code in the HTML, CSS and Javascript you have something that kinda looks like what you had in mind.

Don't use a table!

There is an easier way to create a scrollable table with a fixed header: don't use a table. A table is intended to display a grid in a fixed layout. It is not intended to be flexible with a scrolling option. So if it should display a grid, why not use CSS to create a grid? This example will show you just how to do that. The end result will look like this:

First the HTML

So the HTML for the table is pretty straight forward. There's a Div with the class gridTable which is instead of the usual table element. Next a Div with the class gridHeader instead of the row element that contains the TH elements. 

<div class="gridTable">
   <div class="gridHeader">
      <label>Name</label>
      ... 
   </div>
   <div class="gridRows">
      <div class="gridRow">
         <label>Name 1</label>
         ...
      </div>
   </div>
</div>

Instead of the TR and TD elements I used the Label element. This could also have been a Div or a Span. But if you put dynamic content in it, using asp:Labels or asp:TextBoxes, remember that these use Divs and Spans too. As a result, the styling of your columns might be inherited into those controls, messing up your layout. To avoid that, use something like the Label element instead. You could also do a UL for the Row and Li for the TD. But than you will have to remove the default list styles in the CSS.

Speaking of the CSS

Since we are on the topic of the CSS, let's have a look at that. Since this is all done with pure CSS, we might as well use some CSS variables since some values will be used in different places. What we need is the width of the scrollbar which (if you did not use a specific styling in your CSS) about 17px. And we need the percentages or pixels for the width of the columns. And while we are at it, let's throw in the background-color for the header as well. So that will be:

* {
--scrollbarWidth: 17px;
--header: #003377;
--col1: 25%;
--col2: 25%;
--col3: 20%;
--col4: 15%;
--col5: 15%;
}

Next, the overall layout of the "table" will be this:

.gridTable {
width: 80vw;
background-color: #FAFAFA;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 35px auto;
}

As you can see it is a simple 1 column grid. The first row (the header) is set to 35px and the rest to whatever is left. And since percentages doesn't always work well when the parent element has no specific width, I used the viewport-width (or vw) instead. Making the whole "table" 80% of the inner width of the browser.

Moving on to the header row

The tricky part here is to specify the columns and their width's. Here I used the variables for column 1 to 5 and an extra column to make up for the scrollbar width:

.gridHeader {
width: 100%;
background-color: var(--header);
color: #EEEEEE;
display: grid;
grid-template-columns: var(--col1) var(--col2) var(--col3) var(--col4) var(--col5) var(--scrollbarWidth);
}

The same trick is used later on in the scrollable rows. And there is the advantage of CSS variables. If you want to resize your columns, you can do it in one place while it is executed in various places.

Styling of the header columns

The styling of the columns is pretty straight forward. Same as you would do if you would have been using TD's.

.gridHeader label {
padding: 7px;
padding-top: 11px;
}

But wait, there's more!

Here's the most difficult thing to solve when using a real table. The scrollable rows will have a scrollbar, the header row won't. So you have to make up for that which will take all of you skills and creativity to do. When done with pure CSS, it all becomes a lot easier. You need something extra after something? Ok. Simply use the pseudo element ::after to do just that.

.gridHeader::after {
content: '';
background-color: var(--header);
width: var(--scrollbarWidth);
}

This will put something after the gridHeader with no content but with the same background-color and the scrollbar width. Problem solved!

Now let's do the scrolling

For the rows to scroll, you need to put something around it. In this case that's the Div with the class gridRows. The CSS for that one is:

.gridRows {
width: calc(100% + var(--scrollbarWidth));
height: 150px;
overflow-y: scroll;
}

Of course you can play around with the height in pixels or percentages. In this case I used a fixed height so the scroll effect is visible even with a limited number of rows. Yeah, I was to lazy to put in more than 10 lines is this example.

First the row

The row in the scrollable area is pretty much the same as the gridHeader. minus, of course, the last column to compensate for the scrollbar.

.gridRow {
width: 100%;
background-color: #FAFAFA;
color: #333333;
display: grid;
grid-template-columns: var(--col1) var(--col2) var(--col3) var(--col4) var(--col5);
}

Again I used the column variables. So if I would need to change the width of the columns based on the actual content, that's all done in one place for both the header and the rows.

And finally the columns

Last thing is the styling of the columns, which again is pretty straight forward.

.gridRow label {
padding: 7px;
padding-top: 11px;
}

To wrap it all up

Using a CSS grid instead of a table makes life much easier. The HTML is pretty much similar, even smaller, than using tables. And all can be done using pure CSS. With the use of CSS variables to make it even more easy to maintain.

As usual you can find a working demo on our website. And if you want to have the source code of the example, you can download this from our website too. That's all for this post. I am sure you can do something with this in your own applications. So enjoy and may the source be with you!