Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS-Only Scrollable Table with fixed headers [duplicate]

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.

like image 652
Troy Alford Avatar asked Aug 09 '12 20:08

Troy Alford


People also ask

How do I keep my table header fixed while scrolling in HTML?

To freeze the row/column we can use a simple HTML table and CSS. HTML: In HTML we can define the header row by <th> tag or we can use <td> tag also. Below example is using the <th> tag. We also put the table in DIV element to see the horizontal and vertical scrollbar by setting the overflow property of the DIV element.


10 Answers

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/

like image 119
nkmol Avatar answered Sep 30 '22 14:09

nkmol


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>
like image 36
Purag Avatar answered Sep 30 '22 14:09

Purag


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>
like image 30
Jan Turoň Avatar answered Sep 30 '22 13:09

Jan Turoň


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.

like image 45
Victoria Avatar answered Sep 30 '22 14:09

Victoria


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.

like image 21
Max Alexander Hanna Avatar answered Sep 30 '22 14:09

Max Alexander Hanna


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/

like image 22
Greg Rozmarynowycz Avatar answered Sep 30 '22 13:09

Greg Rozmarynowycz


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

like image 30
BENARD Patrick Avatar answered Sep 30 '22 13:09

BENARD Patrick


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;
}
like image 34
Blunderfest Avatar answered Sep 30 '22 14:09

Blunderfest


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

like image 40
J Belloch Avatar answered Sep 30 '22 14:09

J Belloch


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

like image 36
Kaka Ruto Avatar answered Sep 30 '22 12:09

Kaka Ruto