191

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.

Matthew Cox
  • 13,566
  • 9
  • 54
  • 72
Davis Dimitriov
  • 4,159
  • 3
  • 31
  • 45

10 Answers10

126

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;
}
amgg
  • 546
  • 4
  • 8
Joe
  • 2,641
  • 5
  • 22
  • 43
  • 6
    Best solution for me, the whole row is fixed in contrast to Chinthaka Fernando's solution, where only the text is fixed – SmallestUncomputableNumber Nov 23 '19 at 22:18
  • 3
    Perfect when table has both vertical and horizontal scroll, but remember safari: `position: -webkit-sticky; /* Safari */ position: sticky; /* and other browsers */` – Jørgen Rudolph Låker Mar 17 '20 at 12:21
  • 3
    may also need to add z-index to make sure "sticky" th is on top of table tr's – yob Jun 19 '20 at 15:52
  • 2
    Yep, this is a great answer! And if you want the bottom borders to be maintained see this: https://stackoverflow.com/questions/50361698/border-style-do-not-work-with-sticky-position-element – Jonah Kagan Nov 12 '20 at 20:14
  • Does the `th` need `background: white;`? or the `table` `text-align: left;`? – Stephen Mar 02 '21 at 16:51
  • @Stephen I don't believe so. – Joe Mar 02 '21 at 19:17
  • If you find the first row "drops" down a pixel or two when scrolling, try setting `cellspacing=0` on the table. – rovyko Oct 21 '21 at 15:21
  • 1
    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. – RationalRabbit May 15 '22 at 18:24
  • I encountered a challenge which is that `table` was already defined as `display: block;` which prevents this from working (this is the default in GitHub Markdown rendered tables). I solved this by adding to the `table` CSS a `display: table !important;` line – gene_wood Nov 22 '22 at 23:58
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

Brenton Scott
  • 153
  • 3
  • 13
Matthew Cox
  • 13,566
  • 9
  • 54
  • 72
  • 1
    @Fiesty Mango what is the downside of the imaputz.com version? – turbo2oh Nov 11 '13 at 15:50
  • 1
    @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 – Matthew Cox Nov 11 '13 at 16:16
  • 1
    aren't we breaking accessibility when using the two table approach? – Mark Peterson Apr 21 '16 at 21:20
  • 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. – webzy May 08 '16 at 04:23
  • 12
    all links are broken – PirateApp Jan 15 '18 at 07:02
  • If the browser is resized to be smaller than the width of the table, there is no ability to horizontally scroll right in any of these implementations. – meisenman Nov 29 '18 at 16:47
  • Links are no longer available, please update or delete this answer – Chest Rockwell Jan 14 '19 at 17:45
  • I ended up going with this approach, manually setting the width of the td elements. The only problem was the thead items didn't line up with the items on the tbody because of the scroll bar on the body, so I ended up adding this to the body table to make it the cols line up: ```width: calc(100%-1rem)``` – justdoingmyjob Aug 16 '23 at 15:54
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
}
JaskeyLam
  • 15,405
  • 21
  • 114
  • 149
  • 4
    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. – Jahaziel Jun 08 '16 at 00:28
  • 5
    If I fix the width, the table format will change. How can I make the set the width to auto? – American curl Feb 08 '18 at 07:31
38

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>
Munim Munna
  • 17,178
  • 6
  • 29
  • 58
Tom
  • 1,779
  • 2
  • 15
  • 19
  • 1
    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? – Moiz Tankiwala Aug 20 '16 at 06:33
  • 1
    I haven't found a downside. It definitely handles dynamic column sizing better than anything else I've seen. – Andrew Sep 21 '16 at 20:06
  • 2
    Looks like the header doesn't scroll horizontally when the table is wider than its parent. – alfadog67 May 31 '17 at 14:56
  • 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) – mwag Oct 23 '17 at 04:07
  • 2
    instead of having a height of 160px:``.container { overflow-y: auto; height: 160px; }``, I used ``height: 100vh`` as instructed [here](https://stackoverflow.com/questions/1575141/how-to-make-a-div-100-height-of-the-browser-window). I also set ``html, body {overflow: hidden;}``. – PLG Aug 14 '18 at 21:58
31

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;
}
  • 1
    May also require `background-color` and `z-index` to be set due to how `position: sticky;` works. – OXiGEN Nov 15 '20 at 09:32
  • May also require `table` to set `border-collapse: separate;` so that border styles stay with the sticky cells at all times. – OXiGEN Nov 15 '20 at 09:47
5

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.

Eric Fortis
  • 16,372
  • 6
  • 41
  • 62
  • 7
    columns won't be aligned with the headings (unless they have all fixed width) – ptriek Dec 07 '11 at 22:57
  • 1
    same problem as @zarko's – Dennis Jun 25 '13 at 00:03
  • 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 – mwag Oct 23 '17 at 04:14
5

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

Here's an example

Josh
  • 10,961
  • 11
  • 65
  • 108
  • 3
    as i said in comment on @eric-fortis, this will only work with fixed width columns – ptriek Dec 07 '11 at 23:21
  • 8
    DO NOT USE. Obscures first row of the table body. – Dennis Jun 25 '13 at 00:01
  • I tried (07-jun-2016) and the problem I see is that only the last is visible and also, the width of the 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. – Jahaziel Jun 07 '16 at 13:19
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

artm
  • 8,554
  • 3
  • 26
  • 43
LCIII
  • 3,102
  • 3
  • 26
  • 43
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
    It only works if you define fixed height of the table. For `height!=100%` the header does not stay in the fixed position. – Pavlo Dyban Jan 20 '16 at 08:29
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

webzy
  • 338
  • 3
  • 12