59

As you might know, position: sticky; has landed in Webkit (demo). So far I can see this only works within the parent element. But I'd like to know if I can use this in a scrolling div with a table.

So it needs to 'listen' on the scrolling event of the div, not the table.

I know I can do this with javascript and absolute positioning, but I was wondering if the sticky-positioning would support this.

Willem de Wit
  • 8,604
  • 9
  • 57
  • 90

6 Answers6

122

Position sticky on thead th works in 2018!

In your stylesheets just add this one line:

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

Your table will need to include thead and th for this to style.

<table>
    <thead>
        <tr>
            <th>column 1</th>
            <th>column 2</th>
            <th>column 3</th>
            <th>column 4</th>            
        </tr>    
    </thead>
    <tbody>
      // your body code
    </tbody>
</table>

Also, if you have multiple rows in thead, you can select the first one to remain sticky:

thead tr:first-child th { position: sticky; top: 0; }

As of March 2018 support is pretty much there across modern browsers ref: https://caniuse.com/#feat=css-sticky

Credit goes to @ctf0 for this one (ref comment made 3 Dec 2017)

Evolve
  • 8,939
  • 12
  • 51
  • 63
  • 10
    It doesn’t work as expected (`thead { position: sticky }`) because of this bug: https://bugs.chromium.org/p/chromium/issues/detail?id=702927 – flying sheep Sep 14 '18 at 07:29
  • 1
    works for me with just `thead` as the selector and `overflow: auto` on a parent element of the table. – Walf Oct 10 '18 at 02:26
  • 12
    can it work if i have multiple row th in the thead ? – SantoshK Jan 22 '19 at 12:11
  • 3
    Great answer. The trick is that the position must go on the th element, and the parent cannot have overflow: hidden. – Gustavo Gonçalves Jan 18 '20 at 16:19
  • 3
    Important to add here: firefox is able to sticky the entire THEAD, even if it has multiple TRs. Chrome cannot. You have to sticky all cells individually, and give the first row of cells `top:0` and subsequent rows `top:20px`, `top:40px`, etc... Awful DX – aross Oct 29 '20 at 15:58
  • 1
    Default overflow will be required on a parent element, which you can reset with `overflow:visible`. Sticky is *broken* both for `hidden` and `auto` on a parent element. – Nux May 14 '22 at 16:33
  • @Nux I havent tested what you are saying, but are you suggesting everyone also needs to add this as well as the above? ----> tr { overflow:visible} – Evolve May 23 '22 at 07:06
  • 1
    @Evolve `overflow:visible` is the default. So normally you don't need that. But if you did modify overflow, then you need to revert it to `visible` value. – Nux May 24 '22 at 14:07
14

If you need sticky header for chrome only then you can set position: sticky; top: some_value (top property is mandatory) for td element in a thead element.

See:

<table border=1>
  <thead>
    <tr>
      <td style='position: sticky; top: -1px;background: red'>Sticky Column</td>
      <td>Simple column</td>
    </tr>
  </thead>

table with a stiky header

Ishank
  • 2,860
  • 32
  • 43
bioform
  • 149
  • 1
  • 3
  • 4
    for me the solution was `thead th {position: sticky; top: 0;}` – ctf0 Dec 03 '17 at 12:47
  • This is the better answer imo. Just using `position: sticky` did not work for me. Adding the `top` rule worked perfectly! – Suhas Jul 11 '18 at 05:34
  • 2
    great....been looking for long....this is helpful for chrome browser.....also you need to set background-color and color properly to match up.....may helpful for someone. – nikudale Jan 04 '19 at 15:13
  • 1
    @FilipeEsperandio That's a bug in Chrome/FF that's oddly been marked as resolved in Chrome's tracker. It's frustratingly one of the few things Edge gets right. – Dan Feb 11 '19 at 16:09
11

position: sticky doesn't work with table elements (as long as their display attribute starts with table-) since tables are not part of specification:

Other kinds of layout, such as tables, "floating" boxes, ruby annotations, grid layouts, columns and basic handling of normal "flow" content, are described in other modules.


Edit: As Jul 2019 according to https://caniuse.com/#feat=css-sticky Firefox supports this feature and Chrome has at least support for <th> tag.

czerny
  • 15,090
  • 14
  • 68
  • 96
  • 2
    Not sure about that. Mozilla is apparently working on shipping this: https://bugzilla.mozilla.org/show_bug.cgi?id=975644 – retrovertigo Aug 29 '15 at 06:19
  • 1
    @retrovertigo And it's fixed now. – Qwertie Feb 15 '18 at 00:37
  • @Qwertie Thanks for the update :) "VERIFIED FIXED in Firefox 59" – retrovertigo Feb 15 '18 at 23:47
  • Position Sticky for thead works in 2018, see updated answer below: https://stackoverflow.com/a/49270067/172651 – Evolve Mar 14 '18 at 05:53
  • Correction to the July 2019 edit: Firefox supports `position: sticky` on `thead` elements **on MacOS**. From my testing, Firefox 69 on Windows royally screws it up, ignoring background colour, but not text colour (yielding a completely illegible header) and making the element randomly appear and disappear when scrolling. Webkit doesn’t currently support it for `thead`, treating it as `position: static` (or relative – not sure if the difference is really meaningful for a `thead` element). – Janus Bahs Jacquet Oct 03 '19 at 09:55
2

As it turns out it position: sticky only works in the window and not in a scrolling div.

I created a test-case with a very long table with a table header:

h1 {
  font-size: 18px;
  font-weight: bold;
  margin: 10px 0;
}

div.testTable {
  height: 200px;
  overflow: auto;
}

table.stickyHead thead {
  position: -webkit-sticky;
  top: 0px;
  background: grey;
}

table.stickyHead td,
table.stickyHead th {
  padding: 2px 3px;
}
<h1>Position sticky</h1>
<div class="testTable">
  <table class="stickyHead">
    <thead>
      <tr>
        <th>column 1</th>
        <th>column 2</th>
        <th>column 3</th>
        <th>column 4</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
    </tbody>
  </table>
</div>

As you can see, if you remove the overflow from the wrapper and make your window not so tall, the table-head is sticking to the top of the window. I doesn't apply to the wrapping div even if you make give the div position: relative

ThS
  • 4,597
  • 2
  • 15
  • 27
Willem de Wit
  • 8,604
  • 9
  • 57
  • 90
  • 2
    The test case doesn't work in Firefox nor in Chrome (support for `position:sticky` was probably removed from Blink). Current support http://caniuse.com/#feat=css-sticky – czerny May 03 '15 at 17:30
  • there is a bug for it in bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=975644 – retrovertigo Aug 29 '15 at 06:19
  • 4
    This is not correct (or is no longer correct). I ran through your test and got the sticky header working fine with just two changes: I dropped the `-webkit-` prefix off of the position, and set the position on the th, not the thead: http://jsfiddle.net/xNsaM/128/ Works fine. – Conor Mancone Jun 01 '18 at 13:37
  • 1
    looks like it just works on firefox now, it however does not work if overflow-x is set to auto – Fuseteam Aug 05 '20 at 15:40
1

Setting the position:sticky for the thead is enough, no need to set it for th :

table.StickyHeader thead {
    position: sticky;
    top: 0px;
}

Tested in:

Edge 105.0.1343.42 , Firefox 105.0 , Chrome 105.0.5195.127 , Opera 91.0.4516.16

But, Firefox can not render borders of th when position of thead is set to sticky and th has background-color:

table.StickyHeader th {
    border: 1px solid black;
    padding: 5px;
    background-color: gold;
    text-align: center;
}

Firefox

Edge, Chrome, Opera

  • The easiest solution so far. Also, the only one that worked for me considering a difficult style setup for table – Darya Balu Jun 20 '23 at 09:39
0

Caution:

position:sticky doesn't work anymore on Google Chrome in 2019, try to use fixed instead or display:inline-block

Kostas Minaidis
  • 4,681
  • 3
  • 17
  • 25
lucasvm1980
  • 660
  • 1
  • 9
  • 20
  • 3
    Taken from a previous answer, please be carreful using sticky: This does not work on Chrome v35 through v51, Chrome 52 reenables this with the experimental web platform features flag. Starting from Chrome 56 position: sticky works out of the box. – lucasvm1980 Aug 13 '19 at 13:52