75

I have a solution by which I can create scrollable tables w/fixed header/footer using minor jQuery and CSS - but I am looking for a way to make this a CSS-only solution that is cross-browser compliant.

To be clear, what I am seeking to do is use only a table tag (and it's valid sub-tags, colgroup, col, thead, tbody, tfoot, tr, th, td), but adopt a set of CSS rules which will meet the following conditions:

  1. Must maintain column alignment between header / footer / content rows
  2. Must allow the header/footer to remain fixed while the content scrolls vertically
  3. Must not require any jQuery or other JavaScript in order to provide the functionality
  4. Must only use the tags provided above

This code example: http://jsfiddle.net/TroyAlford/SNKfd/ shows my current approach. Most of the JS is just to populate the table with random values, but the last portion is what drives the left/right scrollability.

$tbody.bind('scroll', function(ev) {
    var $css = { 'left': -ev.target.scrollLeft };
    $thead.css($css);
    $tfoot.css($css);
});

NOTE: The example provided does not render properly in IE, and requires jQuery to provide the horizontal scrolling. I don't care about horizontal scrolling anyway, so it's fine if a solution doesn't do that.

Troy Alford
  • 26,660
  • 10
  • 64
  • 82
  • 1
    http://jsfiddle.net/zLDAq/6/ Been playing around with some stuff, but no real luck as yet. Thought I'd share my "progress" before I called it a night! – jaypeagi Aug 09 '12 at 21:28
  • `overflow-x: hidden;` is the only thing that's missing from your tbody css. – Torsten Walter Aug 09 '12 at 21:58
  • At least in chrome, it is possible to give the thead and tfoot elements a position of fixed: http://jsfiddle.net/ZxPeh/12/. Probably not fully what you want, but it may be useful. – Greg Rozmarynowycz Aug 09 '12 at 22:11
  • I don't know if this is useful, but here is something kind of similar that I did little while ago: http://jsfiddle.net/thundercracker/adD3E/68/ – Greg Rozmarynowycz Aug 09 '12 at 22:14
  • 1
    @TorstenWalter: I think you might have missed my point. :) The scrolling left/right is intentional. The issue is the rendering in IE which doesn't have -any- scrollbars. :) – Troy Alford Aug 10 '12 at 22:29
  • @GregRozmarynowycz: Yes, the fixed position on thead and tfoot works - but it throws off the alignment of the columns, which is not ideal. The ThunderCracker thing you're showing is cool - but isn't just a table which supports scrolling and fixed headers. – Troy Alford Aug 10 '12 at 22:30
  • @TroyAlford Sorry, I can't test in IE. But adding the `overflow-x` statement made the script unnecessary. Safari on Lion by the way doesn't have any scroll bars as well and your example works fine with said addition. – Torsten Walter Aug 10 '12 at 22:33
  • @TorstenWalter: Yes - it works fine in Chrome / Safari / WebKit - but requires JavaScript - and doesn't work at all in IE (which is the major problem) – Troy Alford Aug 10 '12 at 22:37
  • @TroyAlford As I said, using `overflow-x:hidden` allowed me to remove your scroll function and do it in CSS only. http://jsfiddle.net/SNKfd/17/ Or maybe I am still getting something wrong. Anyways, I wish I could do more but I don't have access to IE. – Torsten Walter Aug 10 '12 at 22:41
  • @TorstenWalter: No problem. :) What I'm trying to explain is that removing the scroll function isn't what I'm after. I'm after making it work in IE. Thanks for spending the time - sorry I wasn't more clear. :) I have updated my question to more clearly reflect what I'm looking for. – Troy Alford Aug 10 '12 at 22:54
  • Finally i removed my answer, mission accomplished, whoever expecting this from me. Thank you for your discourage. And i really thankful to Dead.Rabit, supporting me. – John Peter Jan 29 '14 at 04:31
  • 1
    This is the clearest version of this question on the site. I think that we should link the other ones back here. Many of the most reasonable answers do use javascript, though. – dbn Mar 10 '15 at 15:52

13 Answers13

45

This answer will be used as a placeholder for the not fully supported position: sticky and will be updated over time. It is currently advised to not use the native implementation of this in a production environment.

See this for the current support: https://caniuse.com/#feat=css-sticky


Use of position: sticky

An alternative answer would be using position: sticky. As described by W3C:

A stickily positioned box is positioned similarly to a relatively positioned box, but the offset is computed with reference to the nearest ancestor with a scrolling box, or the viewport if no ancestor has a scrolling box.

This described exactly the behavior of a relative static header. It would be easy to assign this to the <thead> or the first <tr> HTML-tag, as this should be supported according to W3C. However, both Chrome, IE and Edge have problems assigning a sticky position property to these tags. There also seems to be no priority in solving this at the moment.

What does seem to work for a table element is assigning the sticky property to a table-cell. In this case the <th> cells.

Because a table is not a block-element that respects the static size you assign to it, it is best to use a wrapper element to define the scroll-overflow.

The code

div {
  display: inline-block;
  height: 150px;
  overflow: auto
}

table th {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
}


/* == Just general styling, not relevant :) == */

table {
  border-collapse: collapse;
}

th {
  background-color: #1976D2;
  color: #fff;
}

th,
td {
  padding: 1em .5em;
}

table tr {
  color: #212121;
}

table tr:nth-child(odd) {
  background-color: #BBDEFB;
}
<div>
  <table border="0">
    <thead>
      <tr>
        <th>head1</th>
        <th>head2</th>
        <th>head3</th>
        <th>head4</th>
      </tr>
    </thead>
    <tr>
      <td>row 1, cell 1</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
  </table>
</div>

In this example I use a simple <div> wrapper to define the scroll-overflow done with a static height of 150px. This can of course be any size. Now that the scrolling box has been defined, the sticky <th> elements will corespondent "to the nearest ancestor with a scrolling box", which is the div-wrapper.


Use of a position: sticky polyfill

Non-supported devices can make use of a polyfill, which implements the behavior through code. An example is stickybits, which resembles the same behavior as the browser's implemented position: sticky.

Example with polyfill: http://jsfiddle.net/7UZA4/6957/

nkmol
  • 8,025
  • 3
  • 30
  • 51
  • 10
    Sticky Table Headers is one of the holy grails of CSS, and the sooner `position: sticky` is widely supported, the better. – Manngo Feb 17 '19 at 01:46
  • I see people find the answer useful, but it looks like major browsers do not display this feature well... – Yasen Oct 02 '19 at 14:22
  • I like this solution. Basically worked the first time (minus my type-os). However, I have "bottomers" (headers on the bottom of the table). I tried putting them inside a tag, but it doesn't keep them in place. Any ideas? – horace Dec 15 '22 at 04:48
  • 1
    @horace Try placing them inside a `` and changing the CSS `top:0` to `bottom:0` – nkmol Dec 15 '22 at 09:05
  • @nkmol Very smart. Forgot to mention I have both headers and footers, so I changed the CSS selector to include thead and then duplicated it and changed thead to tfoot. Works like a charm. – horace Dec 15 '22 at 17:28
  • The following advisory "It is currently advised to not use the native implementation of this in a production environment." has been added to the top of your answer. I don't think that's fair. If this solution doesn't work for you, fine. How can anyone make such a decision about everyone's production environment? There is currently a debate about using AI generated answers. This advisory is a flat generalization that does not bear out in reality. – horace Dec 16 '22 at 03:43
19

Surprised a solution using flexbox hasn't been posted yet.

Here's my solution using display: flex and a basic use of :after (thanks to Luggage) to maintain the alignment even with the scrollbar padding the tbody a bit. This has been verified in Chrome 45, Firefox 39, and MS Edge. It can be modified with prefixed properties to work in IE11, and further in IE10 with a CSS hack and the 2012 flexbox syntax.

Note the table width can be modified; this even works at 100% width.

The only caveat is that all table cells must have the same width. Below is a clearly contrived example, but this works fine when cell contents vary (table cells all have the same width and word wrapping on, forcing flexbox to keep them the same width regardless of content). Here is an example where cell contents are different.

Just apply the .scroll class to a table you want scrollable, and make sure it has a thead:

.scroll {
  border: 0;
  border-collapse: collapse;
}

.scroll tr {
  display: flex;
}

.scroll td {
  padding: 3px;
  flex: 1 auto;
  border: 1px solid #aaa;
  width: 1px;
  word-wrap: break-word;
}

.scroll thead tr:after {
  content: '';
  overflow-y: scroll;
  visibility: hidden;
  height: 0;
}

.scroll thead th {
  flex: 1 auto;
  display: block;
  border: 1px solid #000;
}

.scroll tbody {
  display: block;
  width: 100%;
  overflow-y: auto;
  height: 200px;
}
<table class="scroll" width="400px">
  <thead>
    <tr>
      <th>Header</th>
      <th>Header</th>
      <th>Header</th>
      <th>Header</th>
      <th>Header</th>
      <th>Header</th>
    </tr>
  </thead>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
  <tr>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
    <td>Data</td>
  </tr>
</table>
Yasen
  • 3,400
  • 1
  • 27
  • 22
Purag
  • 16,941
  • 4
  • 54
  • 75
  • 2
    It doesn't seem to maintain the cell width within columns when the cell contents differ in width. – dmvianna Sep 04 '15 at 05:56
  • @dmvianna You're totally right. I'll refine this and see if that's something I can resolve. – Purag Sep 04 '15 at 06:30
  • Nice lightweight solution. I simply added a width to th and td elements to keep them in line. – Felix Böhme Sep 09 '15 at 16:43
  • 7
    I'm sorry about the down-vote but this ain't a solution until it actually solves the problem. No table has identical-width content in every cell. See here: https://jsfiddle.net/npdob14y/1/ – Xharlie Apr 11 '16 at 09:39
  • @Xharlie Okay, [here](https://jsfiddle.net/purmou/npdob14y/4/) is my proposed solution. It may still be inconvenient for each table cell to have the same width, but this way the content makes no difference. Also, I added the CSS for this version to the answer, along with a link to this example. – Purag Apr 12 '16 at 01:50
  • Loving the direction this is headed. When I originally posted the question, Flexbox wasn't a thing - this comes a long way forward. The different width cells & forced word-wrapping are non-ideal, but it's close! – Troy Alford Sep 29 '16 at 17:19
  • 1
    This really is a very **specific** solution to a very specific type of data. I wouldn't say this could be helpful at all, since by settings `tr` to `display:flex` you are eliminating all the good table functionality and then it becomes just a "stupid" block of non-flexiable grid :/ – vsync Jun 14 '17 at 15:56
  • 1
    it also does not seems to handle very well headers with different content https://jsfiddle.net/87449mto/ – santiaago Mar 20 '18 at 15:37
8

Inspired by @Purag's answer, here's another flexbox solution:

/* basic settings */
table { display: flex; flex-direction: column; width: 200px; }
tr { display: flex; }
th:nth-child(1), td:nth-child(1) { flex-basis: 35%; }
th:nth-child(2), td:nth-child(2) { flex-basis: 65%; }
thead, tbody { overflow-y: scroll; }
tbody { height: 100px; }

/* color settings*/
table, th, td { border: 1px solid black; }
tr:nth-child(odd) { background: #EEE; }
tr:nth-child(even) { background: #AAA; }
thead tr:first-child { background: #333; }
th:first-child, td:first-child { background: rgba(200,200,0,0.7); }
th:last-child, td:last-child { background: rgba(255,200,0,0.7); }
<table>
    <thead>
      <tr>
        <th>a
        <th>bbbb
    <tbody>
      <tr>
        <td>fooo vsync dynamic
        <td>bar
      <tr>
        <td>a
        <td>b
      <tr>
        <td>a
        <td>b
      <tr>
        <td>a
        <td>b
      <tr>
        <td>a
        <td>b
      <tr>
        <td>a
        <td>b
      <tr>
        <td>a
        <td>b
  </table>
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
  • 2
    Love this. The css-only sizing is great, and in my mind, no more "ugly" than `width="100px"` applied to the first row (which is commonly needed for sizing columns anyway). – Troy Alford Sep 29 '16 at 17:22
  • Excellent! This is the cleanest and simple solution for the fixed header table I've ever seen. This should be the accepted answer. – Exel Gamboa Apr 04 '17 at 16:06
  • Unfortunately this isn't ideal if you want to scroll horizontally. The header and body have separate scroll panes, so the header columns don't stay aligned with the body columns. – joeytwiddle May 24 '17 at 05:02
  • again, not a good solution because there is not match between the `thead` and the `tbody`, so if you have dynamic content which you cannot possible know the width of, then this wouldn't work. – vsync Jun 14 '17 at 15:57
  • Is it necessary to have the thead "overflow-y:scroll" set? It seems like that row should be fixed. – Roy Wood Sep 26 '17 at 14:40
  • Also, by nesting this in something like a parent div that is a Flexbox container, you can give the table a flex:1 weighting and then set the table width to 100% and the tbody height to 100%, rather than using fixed pixel sizes. – Roy Wood Sep 26 '17 at 14:44
  • Whoever downvoted the answer or posted negative comments has missed the point of the question. Until `position: sticky` is better supported, the solution will be to fake the table by separating the head from the body. This is a nice clean solution which doesn’t impose _too much_ extra burden on the markup or the CSS. – Manngo Feb 17 '19 at 01:41
  • @RoyWood The reason for adding `overflow-y:scroll` for the `thead` is to create the extra padding on the right to match the scroll bar on the body. It’s the only reliable way of getting the width right. – Manngo Feb 17 '19 at 01:42
7

As far as I know, there is no standard way to achieve this with only CSS, although I think there should be. Mozilla browsers used to support fixed headers with a scrolling body, but they've removed it in the last few years.

After researching this a bit, including finding this posting, a friend just developed this solution for me; it uses Javascript but no canned libraries, and the only requirement for the HTML markup is that the table have an id name. Then, at window.onload, to call one Javascript function for each table giving the id, height, and width. If Javascript is disabled at the browser, the whole table is displayed according to its original markup. If Javascript is enabled, the table is fit into the specified height and width, and tbody scrolls, and if thead and tfoot exist, they are fixed at top and bottom.

AbcAeffchen
  • 14,400
  • 15
  • 47
  • 66
Victoria
  • 497
  • 2
  • 10
  • 20
  • 1
    Thank you for the helpful link. My research has led me to basically the same conclusion - though I continue to leave this question open in the hopes it will eventually yield a better answer. This is the kind of functionality which should be far easier to achieve than it currently is. :( – Troy Alford Oct 24 '13 at 06:55
  • Thanks for the link. This is just what I have been searching for for 2 weeks!! – Larry Martell Jul 03 '14 at 17:27
  • This works great on FF but I just tried it on IE and it's not working there. So bummed. – Larry Martell Jul 04 '14 at 19:19
  • Workis fine on IE 11, you must be using an ancient version of IE. – Victoria Jul 04 '14 at 23:12
  • He duplicated the entire table markup 3 times. What a performance hog! – user2867288 Jan 27 '16 at 23:11
  • 1
    Well, it is duplicated only 2 times, for a total of 3 instances. Yes, it could be a performance hog with a huge table or a tiny device, but it doesn't transmit the huge table 3 times. – Victoria Jan 28 '16 at 02:10
5

Ive achieved this easily using this code :

So you have a structure like this :

<table>
<thead><tr></tr></thead>
<tbody><tr></tr></tbody>
</table>

just style the thead with :

<style>
thead{ 
    position: -webkit-sticky;
    position: -moz-sticky;
    position: -ms-sticky;
    position: -o-sticky;
    position: sticky;
    top: 0px;
}
</style>

Three things to consider :

First, this property is new. It’s not supported at all, apart from the beta builds of Webkit-based browsers. So caveat formator. Again, if you really want for your users to benefit from sticky headers, go with a javascript implementation.

Second, if you do use it, you’ll need to incorporate vendor prefixes. Perhaps position: sticky will work one day. For now, though, you need to use position:-webkit-sticky (and the others; check the block of css further up in this post).

Third, there aren’t any positioning defaults at the moment, so you need to at least include top: 0; in the same css declaration as the position:-webkit-sticky. Otherwise, it’ll just scroll off-screen.

Max Alexander Hanna
  • 3,388
  • 1
  • 25
  • 35
3

If you have the option of giving a fixed width to the table cells (and a fixed height to the header), you can used the position: fixed option:

http://jsfiddle.net/thundercracker/ZxPeh/23/

You would just have to stick it in an iframe. You could also have horizontal scrolling by giving the iframe a scrollbar (I think).


Edit 2015

If you can live with a pre-defining the width of your table cells (by percentage), then here's a bit more elegant (CSS-only) solution:

http://jsfiddle.net/7UBMD/77/

Greg Rozmarynowycz
  • 2,037
  • 17
  • 20
  • 5
    Part of the challenge is not using any extra tags. I want this to be a universally applicable set of CSS styles which can be applied to any table without additional mark-up. Otherwise, I could just wrap a table in a table - with the same `` definition, and make the inner table a single cell with overflow scroll. – Troy Alford Aug 12 '12 at 02:01
  • Very nice :) Thank you Greg for providing this live example – Cute_Ninja May 14 '14 at 18:09
3

Only with CSS :

CSS:

tr {
  width: 100%;
  display: inline-table;
  table-layout: fixed;
}

table{
 height:300px;              // <-- Select the height of the table
 display: -moz-groupbox;    // Firefox Bad Effect
}
tbody{
  overflow-y: scroll;      
  height: 200px;            //  <-- Select the height of the body
  width: 100%;
  position: absolute;
}

Bootply : http://www.bootply.com/AgI8LpDugl

BENARD Patrick
  • 30,363
  • 16
  • 99
  • 105
  • what about sizing of columns? it seems they are not set and header columns are not linked to tbody cols! – S.Serpooshan Sep 01 '19 at 08:45
  • @S.Serpooshan Have you check the feedle ? It seems it's working very well for me (and for other users too) [I''ve just checked now with ubuntu and chromium] – BENARD Patrick Sep 01 '19 at 09:14
  • @PimentoWeb Yes i did test it, here is updated feedle: https://www.bootply.com/QVVPFYLXu6 i just changed a cell content as a long text and add the table borders to be able to see the columns better. It is obvious that layout is messed up and cols are not aligned with header! – S.Serpooshan Sep 01 '19 at 10:05
  • @S.Serpooshan Yes I Can see, but it's the normal behaviour because the table length is superior to its container. Look at the top of the fiddle and change col-md-3 per col-md-12, you'll see that everything is alright. – BENARD Patrick Sep 01 '19 at 12:33
1

I see this thread has been inactive for a while, but this topic interested me and now with some CSS3 selectors, this just became easier (and pretty doable with only CSS).

This solution relies on having a max height of the table container. But it is supported as long as you can use the :first-child selector.

Fiddle here.

If anyone can improve on this answer, please do! I plan on using this solution in a commercial app soon!

HTML

<div id="con">
<table>
    <thead>
        <tr>
            <th>Header 1</th>
            <th>Header 2</th>
            <th>Header 3</th>
        </tr>
    </thead>        
    <tbody>             
    </tbody>
</table>
</div>

CSS

#con{
    max-height:300px;
    overflow-y:auto;
}
thead tr:first-child {
    background-color:#00f;
    color:#fff;
    position:absolute;
}
tbody tr:first-child td{
    padding-top:28px;
}
Blunderfest
  • 1,854
  • 1
  • 28
  • 46
  • 1
    Blunderfest, first off -- one of the best implementations I've seen with raw CSS. Secondly, I hate to say, but this doesn't solve the age-old problem of how the `thead > tr > td`'s self-orient their column widths to that of the `tbody`'s. Any solutions you can think of here? However, I appreciate this answer -- Thx! – Cody Jul 01 '14 at 23:07
  • @Cody After trying tons of combinations with percentage widths on the header elements and the body elements, I came up short. Using a set `px` width on the th and td elements works great, but I'm not sure it's a flexible enough solution to share? – Blunderfest Jul 02 '14 at 01:29
  • @SearchForKnowledge Then you use tbody tr:first-child { header styles } and you use tbody nth child rule for the next td. – Blunderfest Feb 05 '15 at 15:53
  • @Cody from what I read from your and Blunderfest's comments, you might like my answer: it's not totaly "self-orientation", but at least percentages were used to set the column widths. – Jan Turoň Dec 01 '16 at 08:20
1

I had the same problem and after spending 2 days researching I found this solution from Ksesocss that fits for me and maybe is good for you too. It allows fixed header and dynamic width and only uses CSS. The only problem is that the source is in spanish but you can find the html and css code there.

This is the link:

http://ksesocss.blogspot.com/2014/10/responsive-table-encabezado-fijo-scroll.html

I hope this helps

J Belloch
  • 39
  • 6
  • For responsive design this is one of the best solutions out there IMO. The only problem I had was alignment getting thrown off when scroll bar is put on the tbody, i had to force unused scroll bar to display the header to maintain alignment. – Steve Snow Jan 22 '16 at 15:47
  • The blog link is set to private. – Mat Gessel May 05 '16 at 17:37
1

if it gets rock hard where all the mentioned solutions don't work (as it got for me), try a two-tabled solutioned, as I explained in this answer

https://stackoverflow.com/a/47722456/6488361

Kaka Ruto
  • 4,581
  • 1
  • 31
  • 39
0

As I was recently in need of this, I will share a solution that uses 3 tables, but does not require JavaScript.

Table 1 (parent) contains two rows. The first row contains table 2 (child 1) for the column headers. The second row contains table 3 (child 2) for the scrolling content.

It must be noted the childTbl must be 25px shorter than the parentTbl for the scroller to appear properly.

This is the source, where I got the idea from. I made it HTML5-friendly without the deprecated tags and the inline CSS.

.parentTbl table {
  border-spacing: 0;
  border-collapse: collapse;
  border: 0;
  width: 690px;
}
.childTbl table {
  border-spacing: 0;
  border-collapse: collapse;
  border: 1px solid #d7d7d7;
  width: 665px;
}
.childTbl th,
.childTbl td {
  border: 1px solid #d7d7d7;
}
.scrollData {
  width: 690;
  height: 150px;
  overflow-x: hidden;
}
<div class="parentTbl">
  <table>
    <tr>
      <td>
        <div class="childTbl">
          <table class="childTbl">
            <tr>
              <th>Header 1</th>
              <th>Header 2</th>
              <th>Header 3</th>
              <th>Header 4</th>
              <th>Header 5</th>
              <th>Header 6</th>
            </tr>
          </table>
        </div>
      </td>
    </tr>
    <tr>
      <td>
        <div class="scrollData childTbl">
          <table>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
            <tr>
              <td>Table Data 1</td>
              <td>Table Data 2</td>
              <td>Table Data 3</td>
              <td>Table Data 4</td>
              <td>Table Data 5</td>
              <td>Table Data 6</td>
            </tr>
          </table>
        </div>
      </td>
    </tr>
  </table>
</div>

This is reliable on different browsers, the downside would be having to hard code the table widths.

k_rollo
  • 5,304
  • 16
  • 63
  • 95
0

I was trying to figure this out myself recently, and I came up with a good solution that works perfectly in my browser (Chrome 51) and supports dynamic column widths. I should mention that after I independently derived my answer I also found a similar technique described elsewhere on the web...

The trick is to use two identical tables positioned on top of one another. The lower table is visible and the upper table is invisible expect for the header. The upper table also has pointer-events: none set on the tbody so mouse interactions hit the underneath table. This way the lower table scrolls under the upper table's header.

For everything to layout and resize properly (when the user adjusts screen width for instance), both tables need to have the same scrollbar behavior. However, the upper table's scroll bar is ignored thanks to the pointer-events: none and can be made invisible with:

<style>
    .hiddenScrollbar::-webkit-scrollbar {
        background-color: transparent;
    }
</style>

Here is the complete code:

<html>

<head>
  <style>
    td {
      border: 1px solid black; 
      white-space: nowrap;
    }
    th {
      background-color: gray;
      border: 1px solid black;
      white-space: nowrap;
    }
    .hiddenScrollbar::-webkit-scrollbar {
      background-color: transparent;
    }
  </style>
</head>

<body>
  Table test. Use mouse wheel to scroll or scroll to the right to see vertical scroll bar. You can also remove the outermost div if you don't want a horizontal scroll bar.
  <br/>
  <div style="display: inline-block; height: 10em; width: 15em; overflow-x: scroll; overflow-y: hidden">
    <div style="position: relative;">
      <div style="display: inline-block; position: absolute; left: 0px; top: 0px; height: 10em; overflow-y: scroll">
        <table style="border-collapse: collapse;">
          <thead>
            <tr>
              <th>Column 1</th>
              <th>Another Column</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Data 1</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 2</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 3</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 4</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 5</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 6</td>
              <td>12340921375021342354235 very long...</td>
            </tr>
            <tr>
              <td>Data 7</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 8</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 9</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 10</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 11</td>
              <td>123409213750213</td>
            </tr>
            <tr>
              <td>Data 12</td>
              <td>123409213750213</td>
            </tr>
          </tbody>
        </table>
      </div>
      <div class="hiddenScrollbar" style="display: inline-block; pointer-events: none; position: relative; left: 0px; top: 0px; height: 10em; overflow-y: scroll">
        <table style="border-collapse: collapse;">
          <thead style="pointer-events: auto">
            <tr>
              <th>Column 1</th>
              <th>Another Column</th>
            </tr>
          </thead>
          <tbody style="visibility: hidden">
            <tr>
              <tr>
                <td>Data 1</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 2</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 3</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 4</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 5</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 6</td>
                <td>12340921375021342354235 very long...</td>
              </tr>
              <tr>
                <td>Data 7</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 8</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 9</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 10</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 11</td>
                <td>123409213750213</td>
              </tr>
              <tr>
                <td>Data 12</td>
                <td>123409213750213</td>
              </tr>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div><br/>
Stuff after table.
</body>

</html>

Caveats: More work is required to prove this works in other browsers. Also I was rather liberal in mixing inline and stylesheet styles. However I believe the general concept is the best way of doing this since if the target browser supports it. The only functionality/display issues are that if you want a horizontal scroll bar, the vertical scroll bar can get scrolled out of view (as shown in the snippet). You can still scroll with the mouse wheel though. Additionally you can't have a transparent background on the header (otherwise the underneath table would show through). Finally you need a way to generate two identical tables. Personally I am using react.js and it is easy to do it with react, but php or other server-side generation or javascript will also work.

bruceceng
  • 1,844
  • 18
  • 23
0

Another implementation but without any overflow on tbody and dynamic columns. Requires JavaScript though. A container div is used to house the column headings. When the table is scrolled past the view port, a fixed header appears at the top. If table is scrolled horizontally, the fixed header scrolls as well.

Column headings are created using span elements with display: inline-block and a negative margin is used to scroll header horizontally. Also optimized using RequestAnimationFrame to avoid any jank.

function rAF(scrollLeft) {
  var offsetLeft = 0 - scrollLeft;
  $('.hdr__inner span:first-child').css('margin-left', offsetLeft);
}

https://codepen.io/lloydleo/pen/NRpqEE