205

I want to make an html table with the top row frozen (so when you scroll down vertically you can always see it).

Is there a clever way to make this happen without javascript?

Note that I do NOT need the left column frozen.

6
  • 2
    can't do it without javascript... Commented Dec 7, 2011 at 22:55
  • 1
    Are you sure that using a table is the best option? What are you trying to achieve? Commented Dec 7, 2011 at 22:56
  • 9
    i suggest checking this out: mkoryak.github.io/floatThead Commented Feb 8, 2014 at 7:06
  • @mkoryak thanks a bunch. had so many issues with datatables' fixedheader plugin, this solved in 5m! great work! Commented Mar 16, 2015 at 12:28
  • Here is a video worth watching that can solve this: youtube.com/watch?v=_dpSEjaKqSE Commented Dec 16, 2021 at 12:53

13 Answers 13

153

I know this has several answers, but none of these really helped me. I found this article which explains why my sticky wasn't operating as expected.

Basically, you cannot use position: sticky; on <thead> or <tr> elements. However, they can be used on <th>.

The minimum code I needed to make it work is as follows:

table { text-align: left; position: relative; } th { background: white; position: sticky; top: 0; } 

With the table set to relative the <th> can be set to sticky, with the top at 0

NOTE: It's necessary to wrap the table with a div with max-height:

<div id="managerTable" > ... </div> 

where:

#managerTable { max-height: 500px; overflow: auto; } 
Sign up to request clarification or add additional context in comments.

9 Comments

Best solution for me, the whole row is fixed in contrast to Chinthaka Fernando's solution, where only the text is fixed
Perfect when table has both vertical and horizontal scroll, but remember safari: position: -webkit-sticky; /* Safari */ position: sticky; /* and other browsers */
may also need to add z-index to make sure "sticky" th is on top of table tr's
Yep, this is a great answer! And if you want the bottom borders to be maintained see this: stackoverflow.com/questions/50361698/…
Simple, versatile and easily augmented to one's needs. I have four tables on my page. Just put each in a separate div and all was fine. 2 scroll bars to deal with - one for the page and one for each table, but fine for my in-house needs.
|
78

This is called Fixed Header Scrolling. There are a number of documented approaches:

http://www.imaputz.com/cssStuff/bigFourVersion.html

You won't effectively pull this off without JavaScript ... especially if you want cross browser support.

There are a number of gotchyas with any approach you take, especially concerning cross browser/version support.

Edit:

Even if it's not the header you want to fix, but the first row of data, the concept is still the same. I wasn't 100% which you were referring to.

Additional thought I was tasked by my company to research a solution for this that could function in IE7+, Firefox, and Chrome.

After many moons of searching, trying, and frustration it really boiled down to a fundamental problem. For the most part, in order to gain the fixed header, you need to implement fixed height/width columns because most solutions involve using two separate tables, one for the header which will float and stay in place over the second table that contains the data.

//float this one right over second table <table> <tr> <th>Header 1</th> <th>Header 2</th> </tr> </table> <table> //Data </table> 

An alternative approach some try is utilize the tbody and thead tags but that is flawed too because IE will not allow you put a scrollbar on the tbody which means you can't limit its height (so stupid IMO).

<table> <thead style="do some stuff to fix its position"> <tr> <th>Header 1</th> <th>Header 2</th> </tr> </thead> <tbody style="No scrolling allowed here!"> Data here </tbody> </table> 

This approach has many issues such as ensures EXACT pixel widths because tables are so cute in that different browsers will allocate pixels differently based on calculations and you simply CANNOT (AFAIK) guarantee that the distribution will be perfect in all cases. It becomes glaringly obvious if you have borders within your table.

I took a different approach and said screw tables since you can't make this guarantee. I used divs to mimic tables. This also has issues of positioning the rows and columns (mainly because floating has issues, using in-line block won't work for IE7, so it really left me with using absolute positioning to put them in their proper places).

There is someone out there that made the Slick Grid which has a very similar approach to mine and you can use and a good (albeit complex) example for achieving this.

https://github.com/6pac/SlickGrid/wiki

8 Comments

@Fiesty Mango what is the downside of the imaputz.com version?
@turbo2oh The downside is he is using the 2nd approach I mentioned (adding a overflow: auto to the tbody). This doesn't work in IE. Try looking at his page in IE and you will see what I mean when you contrast it in Chrome or FF
aren't we breaking accessibility when using the two table approach?
You won't effectively pull this off without javascript ... especially if you want cross browser support..... yes you can. See my css solution below. And easy to do as well with css only.
all links are broken
|
50

According to Pure CSS Scrollable Table with Fixed Header , I wrote a DEMO to easily fix the header by setting overflow:auto to the tbody.

table thead tr{ display:block; } table th,table td{ width:100px;//fixed width } table tbody{ display:block; height:200px; overflow:auto;//set tbody to auto } 

2 Comments

I like the simplicity of this aproach. However, since I din't have the requirement of being javascript free, and wanted the auto-width feature, I modified this solution to fit. When I removed the fixed width line, the width of headers was off, so I wrote a short javascript that copies the width of the first data row to the header row, cell by cell, using getBoundingClientRect() and the DOM rows and cells collections.
If I fix the width, the table format will change. How can I make the set the width to auto?
39

My concern was not to have the cells with fixed width. Which seemed to be not working in any case. I found this solution which seems to be what I need. I am posting it here for others who are searching of a way. Check out this fiddle

Working Snippet:

html, body{ margin:0; padding:0; height:100%; } section { position: relative; border: 1px solid #000; padding-top: 37px; background: #500; } section.positioned { position: absolute; top:100px; left:100px; width:800px; box-shadow: 0 0 15px #333; } .container { overflow-y: auto; height: 160px; } table { border-spacing: 0; width:100%; } td + td { border-left:1px solid #eee; } td, th { border-bottom:1px solid #eee; background: #ddd; color: #000; padding: 10px 25px; } th { height: 0; line-height: 0; padding-top: 0; padding-bottom: 0; color: transparent; border: none; white-space: nowrap; } th div{ position: absolute; background: transparent; color: #fff; padding: 9px 25px; top: 0; margin-left: -25px; line-height: normal; border-left: 1px solid #800; } th:first-child div{ border: none; }
<section class=""> <div class="container"> <table> <thead> <tr class="header"> <th> Table attribute name <div>Table attribute name</div> </th> <th> Value <div>Value</div> </th> <th> Description <div>Description</div> </th> </tr> </thead> <tbody> <tr> <td>align</td> <td>left, center, right</td> <td>Not supported in HTML5. Deprecated in HTML 4.01. Specifies the alignment of a table according to surrounding text</td> </tr> <tr> <td>bgcolor</td> <td>rgb(x,x,x), #xxxxxx, colorname</td> <td>Not supported in HTML5. Deprecated in HTML 4.01. Specifies the background color for a table</td> </tr> <tr> <td>border</td> <td>1,""</td> <td>Specifies whether the table cells should have borders or not</td> </tr> <tr> <td>cellpadding</td> <td>pixels</td> <td>Not supported in HTML5. Specifies the space between the cell wall and the cell content</td> </tr> <tr> <td>cellspacing</td> <td>pixels</td> <td>Not supported in HTML5. Specifies the space between cells</td> </tr> <tr> <td>frame</td> <td>void, above, below, hsides, lhs, rhs, vsides, box, border</td> <td>Not supported in HTML5. Specifies which parts of the outside borders that should be visible</td> </tr> <tr> <td>rules</td> <td>none, groups, rows, cols, all</td> <td>Not supported in HTML5. Specifies which parts of the inside borders that should be visible</td> </tr> <tr> <td>summary</td> <td>text</td> <td>Not supported in HTML5. Specifies a summary of the content of a table</td> </tr> <tr> <td>width</td> <td>pixels, %</td> <td>Not supported in HTML5. Specifies the width of a table</td> </tr> </tbody> </table> </div> </section>

5 Comments

I had a quick view of the jsfiddle link suggested by @Tom and this seems to be an excellent solutions. Wonder why this has not been marked as an answer. The Pros are - No Javascript, table contents determine the width of the columns. Has anybody found any downside?
I haven't found a downside. It definitely handles dynamic column sizing better than anything else I've seen.
Looks like the header doesn't scroll horizontally when the table is wider than its parent.
this solution uses a fixed div to set on top of the TH, which has the drawback of requiring maintenance of two objects for each TH, which can be very burdensome to maintain as more features are added (such as column resizing, ellipsis overflow, etc). Using a very slight modification of the pure CSS solution, you can still have contents determine width by removing "display:block" from the CSS, and then, after the table is rendered, using javascript to modify table.tHead and table.tBodies to change display to 'block' (either via style or className)
instead of having a height of 160px:.container { overflow-y: auto; height: 160px; }, I used height: 100vh as instructed here. I also set html, body {overflow: hidden;}.
33

You can use CSS position: sticky; for the first row of the table MDN ref:

.table-class tr:first-child>td{ position: sticky; top: 0; } 

2 Comments

May also require background-color and z-index to be set due to how position: sticky; works.
May also require table to set border-collapse: separate; so that border styles stay with the sticky cells at all times.
5

It is possible using position:fixed on <th> (<th> being the top row).

Here's an example

3 Comments

as i said in comment on @eric-fortis, this will only work with fixed width columns
DO NOT USE. Obscures first row of the table body.
I tried (07-jun-2016) and the problem I see is that only the last <th> is visible and also, the width of the <th> seems to be in no way related to the corresponding column. The reported obscuring of the first row may be due to not using the margin-top attribute for the table class in the css.
4

you can use two divs one for the headings and the other for the table. then use

#headings { position: fixed; top: 0px; width: 960px; } 

as @ptriek said this will only work for fixed width columns.

3 Comments

columns won't be aligned with the headings (unless they have all fixed width)
same problem as @zarko's
this introduces many other problems if you ever want to add other table features like resizing, text-overflow etc. in addition, it seems unnecessarily complicated when compared to other (e.g. pure css or similar) solutions
0

The Chromatable jquery plugin allows a fixed header (or top row) with widths that allow percentages--granted, only a percentage of 100%.

http://www.chromaloop.com/posts/chromatable-jquery-plugin

I can't think of how you could do this without javascript.

update: new link -> http://www.jquery-plugins.info/chromatable-00012248.htm

Comments

0

I use this:

tbody{ overflow-y: auto; height: 350px; width: 102%; } thead,tbody{ display: block; } 

I define the columns width with bootstrap css col-md-xx. Without defining the columns width the auto-width of the doesn't match the . The 102% percent is because you lose some sapce with the overflow

1 Comment

It only works if you define fixed height of the table. For height!=100% the header does not stay in the fixed position.
0

Using css zebra styling

Copy paste this example and see the header fixed.

 <style> .zebra tr:nth-child(odd){ background:white; color:black; } .zebra tr:nth-child(even){ background: grey; color:black; } .zebra tr:nth-child(1) { background:black; color:yellow; position: fixed; margin:-30px 0px 0px 0px; } </style> <DIV id= "stripped_div" class= "zebra" style = " border:solid 1px red; height:15px; width:200px; overflow-x:none; overflow-y:scroll; padding:30px 0px 0px 0px;" > <table> <tr > <td>Name:</td> <td>Age:</td> </tr> <tr > <td>Peter</td> <td>10</td> </tr> </table> </DIV> 

Notice the top padding of of 30px in the div leaves space that is utilized by the 1st row of stripped data ie tr:nth-child(1) that is "fixed position" and formatted to a margin of -30px

1 Comment

you lose cell widths with this.
0

I know that vanilla styling was asked for, but due to the prevalence of TailwindCSS, I thought it also useful for a version that uses Tailwind styling as well:

<div class="overflow-y-auto max-h-[500px]"> <!-- Adjust max-height as needed --> <table class="min-w-full divide-y divide-gray-300"> <thead class="bg-white sticky top-0 z-10"> <!-- Add these classes --> <tr> <!-- Your existing th elements --> </tr> </thead> <tbody class="divide-y divide-gray-200 bg-white"> <!-- Your existing tbody content --> </tbody> </table> </div> 

Comments

0

Whenever there are many solutions to a problem, the shortest solution with minimum code is the best one. My table is generated using pd.DataFrame.to_html(), all I need to do is to append the following into CSS-style:

thead > tr > th { position: sticky; top: 0; } 

It works out perfectly. If you just put th {, you will end up with all rows of the index column overlapped together at the top when you scroll to the bottom. And you won't be able to see the name of the index column. If you also need to freeze the left-most column when scrolling horizontally, you can append the following in addition:

tbody > tr > th { position: sticky; left: 0; } 

Comments

0

Making the TRs flex boxes and the TBODY a block with a max height and overflow scrolling seems to do the trick for me:

table.sticky-headers tbody { overflow: scroll; max-height: 80vh; display: block; } table.sticky-headers tr { display: flex; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.