Introduction
This article shows one of the possible ways to freeze HTML table columns and rows with CSS only, without JavaScript or JQuery.
Background
I would like to thank Manish Malviya for sharing his knowledge of freezing the table row column in this blog post.
The following work is inspired by his article.
Youtube Tutorial Video Walkthrough
Examples
Example 1: As shown in Youtube Tutorial Video
Example 2: Fixed width & height
Example 3: Responsive width & height (adjusted by using viewport)
Using the Code
Let's take a simple HTML Table as example:
<div class="div1">
<table>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
<th>Column 5</th>
<th>Column 6</th>
</tr>
<tr>
<td>Row Data 1</td>
<td>Row Data 2</td>
<td>Row Data 3</td>
<td>Row Data 4</td>
<td>Row Data 5</td>
<td>Row Data 6</td>
</tr>
<tr>
<td>Row Data 1</td>
<td>Row Data 2</td>
<td>Row Data 3</td>
<td>Row Data 4</td>
<td>Row Data 5</td>
<td>Row Data 6</td>
</tr>
<tr>
<td>Row Data 1</td>
<td>Row Data 2</td>
<td>Row Data 3</td>
<td>Row Data 4</td>
<td>Row Data 5</td>
<td>Row Data 6</td>
</tr>
</table>
</div>
Here is the CSS that does the magic:
.div1 {
width: 600px;
height: 400px;
overflow: scroll;
border: 1px solid #777777;
}
.div1 table {
border-spacing: 0;
}
.div1 th {
border-left: none;
border-right: 1px solid #bbbbbb;
padding: 5px;
width: 80px;
min-width: 80px;
position: sticky;
top: 0;
background: #727272;
color: #e0e0e0;
font-weight: normal;
}
.div1 td {
border-left: none;
border-right: 1px solid #bbbbbb;
border-bottom: 1px solid #bbbbbb;
padding: 5px;
width: 80px;
min-width: 80px;
}
.div1 th:nth-child(1),
.div1 td:nth-child(1) {
position: sticky;
left: 0;
width: 150px;
min-width: 150px;
}
.div1 th:nth-child(2),
.div1 td:nth-child(2) {
position: sticky;
left: 161px;
width: 50px;
min-width: 50px;
}
.div1 td:nth-child(1),
.div1 td:nth-child(2) {
background: #ffebb5;
}
.div1 th:nth-child(1),
.div1 th:nth-child(2) {
z-index: 2;
}
Explanation
First of all, a DIV
tag is used to contain the TABLE
, providing a fixed width and height to turn a very long and wide HTML table into scrollable table.
.div1 {
width: 600px;
height: 400px;
overflow: scroll;
border: 1px solid #777777;
}
The attribute of “overflow: scroll
” will make the table scrollable.
For building a responsive table, the CSS function “CALC
” can be used to auto calculate the width, such as:
.div1 {
height: calc(100vh - 250px);
width: calc(100vw - 100px);
overflow: scroll;
border: 1px solid #777777;
}
Please note that the space is required in between the CALC
values.
For example, this is wrong:
height: calc(100vh-250px);
and this is correct:
height: calc(100vh - 250px);
“vh
” or “vw
” are “Viewport” units.
100vh
= 100% visible height, it’s something like window size, it refers to the visible area. 100vw
= 100% visible width.
Styling the TABLE
.div1 table {
border-spacing: 0;
}
border-spacing: 0
, eliminates the empty distance between cells
Note that the attribute of "border-collapse: collapse
" cannot be used in this case. This is because the border line will behave incorrectly with "position: sticky
" which will be discussed later below.
Styling the TH (header)
.div1 th {
border-left: none;
border-right: 1px solid #bbbbbb;
padding: 5px;
width: 80px;
min-width: 80px;
position: sticky;
top: 0;
background: #727272;
color: #e0e0e0;
font-weight: normal;
}
position: sticky
, this will make the TH
cells always stay at top position top: 0
, this tells TH
cells to always stay at position 0 (zero) measured from top background
, without background color, the bottom TD
cells will “crash” into TH
cells, making them overlap with each other width, min-width
: this is used to fix the column width, without these attributes, the cell columns will be deformed and compressed
This will freeze the “Header”.
Freezing the 1st Column
.div1 th:nth-child(1),
.div1 td:nth-child(1) {
position: sticky;
left: 0;
width: 150px;
min-width: 150px;
}
nth-child(1)
means the first element in each “TR” block. Refers to 1st column. left: 0
tells the cells to "freeze" at position zero from left.
Freezing the 2nd Column
.div1 th:nth-child(2),
.div1 td:nth-child(2) {
position: sticky;
left: 161px;
width: 50px;
min-width: 50px;
}
Calculation of next cell position is: Border Width + Padding + Cell Width
In this case, 0 left border width + 5px left padding + 150px (1st cell width) + 5px right padding) + 1px right border width = 161px.
Hence, left: 161px
Next, when the table is scrolled to the right, the non-sticky cells will be crashing into and overlapping with the 1st and 2nd frozen cells.
Provide a background color for 1st and 2nd frozen cells to fix the overlapping issue:
.div_maintb td:nth-child(1),
.div_maintb td:nth-child(2) {
background: #ffebb5;
}
Now, the first two frozen “TH
” and “TD
” are both “sticky”. Since "TD" is created after "TH", when the table is being scrolled down, the “TD
” will stay on top and cover up the “TH
”, making “TH
” hide under “TD
”.
Thus, we can set the CSS value of "z-index
" of "TH
" to override it's layer to be brought to front/top, so that "TD
" will now go under/behind "TH
".
By default, all elements has default value of "z-index=0
".
.div1 th:nth-child(1),
.div1 th:nth-child(2) {
z-index: 2;
}