Welcome to a quick tutorial and examples on how to freeze rows and columns in HTML tables. Want to “fix a table row or column” in HTML? Only to find out that there are no direct ways in HTML?
The possible ways to freeze or fix a row/column in HTML and CSS are:
- Limit the height of the table body and set it to auto overflow.
thead, tbody { display: block; }
tbody { max-height: 100px; overflow: auto; }
- Set a sticky table header –
th { position: sticky; top: 0; }
- To freeze a column:
- Set the first cell of the row as a header –
<tr> <th>HEAD</th> <td>CELL</td><td>CELL</td> </tr>
- Set the header cell to stick –
th { position: sticky; left: 0; }
- Set the first cell of the row as a header –
That covers the quick basics, but read on for detailed examples!
ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.
TLDR – QUICK SLIDES
Fullscreen Mode – Click Here
TABLE OF CONTENTS
DOWNLOAD & NOTES
Firstly, here is the download link to the example code as promised.
QUICK NOTES
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.
EXAMPLE CODE DOWNLOAD
Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
FREEZE TABLE ROWS & COLUMNS
All right, let us now get the various possible ways to freeze the table rows and columns in HTML.
METHOD 1) FREEZE HEADER ROW WITH OVERFLOW
<style>
/* (A) DISPLAY AS BLOCK */
#demoA thead, #demoA tbody { display: block; }
/* (B) LIMIT HEIGHT & SET OVERFLOW */
#demoA tbody {
max-height: 150px;
overflow: auto;
}
/* (C) COSMETICS - NOT IMPORTANT */
#demoA { width:100%; }
#demoA th, #demoA td {
width: 100px;
font-size: 20px;
padding: 10px;
text-align: left;
}
#demoA thead { background: #ffebec; }
#demoA tbody { background: #e4ebff; }
</style>
<table id="demoA">
<thead>
<tr><th>Head 1</th><th>Head 2</th></tr>
</thead>
<tbody>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
</tbody>
</table>
Head | Head |
---|---|
Cell | Cell |
Cell | Cell |
Cell | Cell |
Cell | Cell |
Cell | Cell |
This seems to be a common solution on the Internet. It is not the prettiest, but at least it is simple and it works.
- Just set the head and body sections of the table to
display: block
. - Then limit the height of the table body, set it to auto overflow
tbody { max-height: XYZpx; overflow: auto; }
METHOD 2) STICKY HEADER
/* (A) STICKY HEADER */
#demoB th {
position: sticky;
top: 0;
z-index: 2;
}
/* (B) COSMETICS - NOT IMPORTANT */
#demoB { width:100%; }
#demoB th, #demoB td {
font-size: 20px;
padding: 10px;
text-align: left;
}
#demoB th { background: #ffebec; }
#demoB td { background: #e4ebff; }
</style>
<table id="demoB">
<thead>
<tr><th>Head 1</th><th>Head 2</th></tr>
</thead>
<tbody>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
</tbody>
</table>
Head | Head |
---|---|
Cell | Cell |
Cell | Cell |
Cell | Cell |
Cell | Cell |
Cell | Cell |
This is an alternative to the previous method, where we set the table header to position: sticky
. Personally, I think this is better and more elegant.
METHOD 3) FREEZE COLUMN
<style>
/* (A) TABLE WRAPPER */
#demoCW {
width: 100%;
overflow: auto;
}
/* (B) STICKY HEADERS */
#demoCT th {
position: sticky;
left: 0;
z-index: 2;
}
/* (C) COSMETICS - NOT IMPORTANT */
#demoCT th, #demoCT td {
font-size: 20px;
padding: 10px;
text-align: left;
min-width: 300px;
}
#demoCT th { background: #ffebec; }
#demoCT td { background: #e4ebff; }
</style>
<div id="demoCW"><table id="demoCT">
<tr>
<th>Head</th>
<td>Cell</td><td>Cell</td><td>Cell</td>
</tr>
<tr>
<th>Head</th>
<td>Cell</td><td>Cell</td><td>Cell</td>
</tr>
</table></div>
Head | Cell | Cell | Cell |
---|---|---|---|
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
- Look no further, this is the same old
position: sticky
, except that we set it toleft: 0
instead. - Also, the first cell of each row is a
<th>
.
METHOD 4) FREEZE ROW & COLUMN
<style>
/* (A) TABLE WRAPPER */
#demoDW {
width: 100%;
max-height: 200px;
overflow: auto;
}
/* (B) STICKY HEADERS */
#demoDT th {
position: sticky;
top: 0;
z-index: 2;
}
#demoDT th[scope=row] {
left: 0;
z-index: 1;
}
/* (C) COSMETICS - NOT IMPORTANT */
#demoDT th, #demoDT td {
font-size: 20px;
padding: 10px;
text-align: left;
min-width: 300px;
}
#demoDT th { background: #ffebec; }
#demoDT td { background: #e4ebff; }
</style>
<div id="demoDW"><table id="demoDT">
<thead>
<tr>
<th>Head</th><th>Head</th>
<th>Head</th><th>Head</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Head</th>
<td>Cell</td><td>Cell</td><td>Cell</td>
</tr>
<tr>
<th scope="row">Head</th>
<td>Cell</td><td>Cell</td><td>Cell</td>
</tr>
</tbody>
</table></div>
Head | Head | Head | Head |
---|---|---|---|
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
Head | Cell | Cell | Cell |
Lastly, how do we stick both the header row and first column? Keep calm and look closely – This is just a combination of using position: sticky
on both the header and first column.
EXTRA BITS & LINKS
That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.
LINKS & REFERENCES
- Fixed Table Headers – Adrain Roselli
- Freezing Row and Column in HTML Table Using CSS – Perficient
INFOGRAPHIC CHEAT SHEET

THE END
Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
how about 2 sticky column?
Not tested, but you can try using
colgroup
and sticky the group instead…Sweet little solution