Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

HTML Table – Freeze Row and Column with CSS

5.00/5 (6 votes)
15 Dec 2021CPOL3 min read 48.7K  
Having a freeze pane effect on HTML Table
Freeze/Fixed/Frozen the Columns and Rows in HTML Table by using CSS

Image 1

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

Image 2

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:

HTML
<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;
    /* 1st cell left/right padding + 1st cell width + 1st cell left/right border width */
    /* 0 + 5 + 150 + 5 + 1 */
    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.

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

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

CSS
height: calc(100vh-250px);

and this is correct:

CSS
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

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

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

CSS
.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;
    /* 1st cell left/right padding + 1st cell width + 1st cell left/right border width */
    /* 0 + 5 + 150 + 5 + 1 */
    left: 161px;
    width: 50px;
    min-width: 50px;
}

Calculation of next cell position is: Border WidthPadding + 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:

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

CSS
.div1 th:nth-child(1),
.div1 th:nth-child(2) {
    z-index: 2;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)