419

I need a simple solution. I know it's similar to some other questions, like:

But I need just a single left column to be frozen and I would prefer a simple and script-less solution.

1
  • Cool question. Please make sure you test the final solution with a utility like browserstack. Well done. Commented Jun 5 at 4:11

24 Answers 24

477

If you want a table where only the columns scroll horizontally, you can position: absolute the first column (and specify its width explicitly), and then wrap the entire table in an overflow-x: scroll block. Don't bother trying this in IE7, however...

Relevant HTML & CSS:

table {
  border-collapse: separate;
  border-spacing: 0;
  border-top: 1px solid grey;
}

td,
th {
  margin: 0;
  border: 1px solid grey;
  white-space: nowrap;
  border-top-width: 0px;
}

div {
  width: 500px;
  overflow-x: scroll;
  margin-left: 5em;
  overflow-y: visible;
  padding: 0;
}

.headcol {
  position: absolute;
  width: 5em;
  left: 0;
  top: auto;
  border-top-width: 1px;
  /*only relevant for first row*/
  margin-top: -1px;
  /*compensate for top border*/
}

.headcol:before {
  content: 'Row ';
}

.long {
  background: yellow;
  letter-spacing: 1em;
}
<div>
  <table>
    <tr>
      <th class="headcol">1</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">2</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">3</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">4</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">5</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">6</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
  </table>
</div>

Fiddle

Sign up to request clarification or add additional context in comments.

30 Comments

This doesn't handle arbitrary labels. Unless you have very predictably short labels, you will get this: jsfiddle.net/YMvk9/3724
@AaronLS: yep, you need to specify the width of the first column. If you need to deal with arbitrary-length labels, you could use text-overflow: ellipsis to cleanly display those.
@EamonNerbonne ellipsis was a good option I considered. Another option was to do something like this. Repeating the first column hidden as well as fixed. The second hidden column ensures the height of the rows syncs. Only works if you have a fixed width column though: jsfiddle.net/abkNM/2224
For anyone who is interested. Essentially the solution moves the headers off from the table and set each of them a 5em width. The table itself is pushed to the right for the same 5em width. This makes a visual appearance that the headers are still part of the normal flow in the table.
Here is a smaller example without unnecessary code jsfiddle.net/kashesandr/rLv5b1ft/1
|
220

You can use sticky position. Here is a sample code. This is HTML/CSS solution. No js is required.

.view {
  margin: auto;
  width: 600px;
}

.wrapper {
  position: relative;
  overflow: auto;
  border: 1px solid black;
  white-space: nowrap;
}

.sticky-col {
  position: -webkit-sticky;
  position: sticky;
  background-color: white;
}

.first-col {
  width: 100px;
  min-width: 100px;
  max-width: 100px;
  left: 0px;
}

.second-col {
  width: 150px;
  min-width: 150px;
  max-width: 150px;
  left: 100px;
}
<div class="view">
  <div class="wrapper">
    <table class="table">
      <thead>
        <tr>
          <th class="sticky-col first-col">Number</th>
          <th class="sticky-col second-col">First Name</th>
          <th>Last Name</th>
          <th>Employer</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="sticky-col first-col">1</td>
          <td class="sticky-col second-col">Mark</td>
          <td>Ham</td>
          <td>Micro</td>
        </tr>
        <tr>
          <td class="sticky-col first-col">2</td>
          <td class="sticky-col second-col">Jacob</td>
          <td>Smith</td>
          <td>Adob Adob Adob AdobAdob Adob Adob Adob Adob</td>
        </tr>
        <tr>
          <td class="sticky-col first-col">3</td>
          <td class="sticky-col second-col">Larry</td>
          <td>Wen</td>
          <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

codeply code: https://www.codeply.com/p/oZ4NjpvwbO

17 Comments

Sure, this is the easiest and the most logical way to do it, I did only: $("table th:first").css("position", "sticky").css("left", "0px").css("background-color", $("body").css("background-color")); and in loop for every row and that was it!
This should be the accepted answer. No position: absolute hacking and table cell sizes are not fixed.
is it possible to make only 1 column sticky? I am trying your link but i am unable to make it? Like i want to make employer column sticky
This is by far the best answer. Nothing is better than stickying the columns. Works great on Chrome, Safari and Firefox. Didn't test it on other browsers.
Great solution. Here is a Plunker based on this answer with fixed columns, fixed header, and non even row heights plnkr.co/plunk/l0m9pF3His2BrKjS
|
66

For most browsers released after 2017:

You can use the position: sticky. See https://caniuse.com/#feat=css-sticky.

There is no need for a fixed width column.

Run the code snippet below to see how it works.

.tscroll {
  width: 400px;
  overflow-x: scroll;
  margin-bottom: 10px;
  border: solid black 1px;
}

.tscroll table td:first-child {
  position: sticky;
  left: 0;
  background-color: #ddd;
}

.tscroll td, .tscroll th {
  border-bottom: dashed #888 1px;
}
<html>
<div class="tscroll">
  <table>
    <thead>
      <tr>
        <th></th>
        <th colspan="5">Heading 1</th>
        <th colspan="8">Heading 2</th>
        <th colspan="4">Heading 3</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>9:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>10:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>11:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>12:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>13:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>14:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>15:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>16:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>17:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
    </tbody>
    </table>
</div>

10 Comments

Doing away with fixed with is of interest to me. However, I don't see how this solution could scale to 2+ sticky columns without fixed width for the sticky columns coming into play, since it seems we need an accurate left property on all sticky columns.
@Ben There are several ways to deal with this depending on your situation. If the first column has a known width, then set the left property on the second column to be the width of the first column. If that does not work for you, a nested table should work. If you don't like the idea of a nested table, you can also use nested divs with table-like properties.
Well, you have to use Shift+Mousescroll to scroll left-right, which almost nobody knows. I've implemented a solution which solves this, but it requires some jQuery, which doesn't meet the requirements of the original post.
@Savage True enough, I am now mostly using the Magic Mouse 2 which allows you to scroll both horizontally and vertically by swiping your finger. It is the default mouse for Mac, but you can install the drivers on Windows for scrolling to work there as well. Not a solution for everyone, but worth a mention anyhow. The only annoyance is you can't use it and charge it at the same time.
@Savage, I for one did not know about the Shift+Mousescroll trick, but now I do thanks to you!
|
21

In case of fixed width left column the best solution is provided by Eamon Nerbonne.

In case of variable width left column the best solution I found is to make two identical tables and push one above another. Demo: http://jsfiddle.net/xG5QH/6/.

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
/* important styles */

.container {
   /* Attach fixed-th-table to this container,
      in order to layout fixed-th-table
      in the same way as scolled-td-table" */
   position: relative;

   /* Truncate fixed-th-table */
   overflow: hidden;
}

.fixed-th-table-wrapper td,
.fixed-th-table-wrapper th,
.scrolled-td-table-wrapper td,
.scrolled-td-table-wrapper th {
   /* Set background to non-transparent color
      because two tables are one above another.
    */
   background: white;
}
.fixed-th-table-wrapper {
   /* Make table out of flow */
   position: absolute;
}
.fixed-th-table-wrapper th {
    /* Place fixed-th-table th-cells above 
       scrolled-td-table td-cells.
     */
    position: relative;
    z-index: 1;
}
.scrolled-td-table-wrapper td {
    /* Place scrolled-td-table td-cells
       above fixed-th-table.
     */
    position: relative;
}
.scrolled-td-table-wrapper {
   /* Make horizonal scrollbar if needed */
   overflow-x: auto;
}


/* Simulating border-collapse: collapse,
   because fixed-th-table borders
   are below ".scrolling-td-wrapper table" borders
*/

table {
    border-spacing: 0;
}
td, th {
   border-style: solid;
   border-color: black;
   border-width: 1px 1px 0 0;
}
th:first-child {
   border-left-width: 1px;
}
tr:last-child td,
tr:last-child th {
   border-bottom-width: 1px;
}

/* Unimportant styles */

.container {
    width: 250px;
}
td, th {
   padding: 5px;
}
</style>
</head>

<body>
<div class="container">

<div class="fixed-th-table-wrapper">
<!-- fixed-th-table -->
<table>
    <tr>
         <th>aaaaaaa</th>
         <td>ccccccccccc asdsad asd as</td>
         <td>ccccccccccc asdsad asd as</td>
    </tr>
    <tr>
         <th>cccccccc</th>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
    </tr>
</table>
</div>

<div class="scrolled-td-table-wrapper">
<!-- scrolled-td-table
     - same as fixed-th-table -->
<table>
    <tr>
         <th>aaaaaaa</th>
         <td>ccccccccccc asdsad asd as</td>
         <td>ccccccccccc asdsad asd as</td>
    </tr>
    <tr>
         <th>cccccccc</th>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
    </tr>
</table>
</div>

</div>
</body>
</html>

2 Comments

+10 very clever. Had to set border-collapse to seperate under bootstap, other wise the borders don't float with the cells. Bit of border cleaning required after that.
Very Nice! Thank You! i didn't use <th> for my own reasons, i used a class to indicate which column were to be fixed, and, for good measure, added a visibility: collapse; to the columns to hide from the fixed table (gotta love the :not() css selector!). i also used jQuery to .clone() the table, once it has been generated by php+MySQL+ajax, and insert it into a cleaned out div...
21

You can just make the first column position: sticky; z-index: 9. It will make the column/row stick to its current position. Checkout my example codepen here https://codepen.io/swastikmishra/pen/zYYdKBQ

HTML Example

table {
  text-align: center;
}

.table-container {
  width: 500px;
  height: 300px;
  overflow: scroll;
}

table th,
table td {
  white-space: nowrap;
  padding: 10px 20px;
  font-family: Arial;
}

table tr th:first-child,
table td:first-child {
  position: sticky;
  width: 100px;
  left: 0;
  z-index: 10;
  background: #fff;
}

table tr th:first-child {
  z-index: 11;
}

table tr th {
  position: sticky;
  top: 0;
  z-index: 9;
  background: #fff;
}
<div class="table-container">
  <table>
    <tr>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
    </tr>
    <tr>
      <td>H11</td>
      <td>H12</td>
      <td>H13</td>
      <td>H14</td>
      <td>H15</td>
      <td>H16</td>
      <td>H17</td>
    </tr>
    <tr>
      <td>H21</td>
      <td>H22</td>
      <td>H23</td>
      <td>H24</td>
      <td>H25</td>
      <td>H26</td>
      <td>H27</td>
    </tr>
    <tr>
      <td>H31</td>
      <td>H32</td>
      <td>H33</td>
      <td>H34</td>
      <td>H35</td>
      <td>H36</td>
      <td>H37</td>
    </tr>
    <tr>
      <td>H41</td>
      <td>H42</td>
      <td>H44</td>
      <td>H44</td>
      <td>H45</td>
      <td>H46</td>
      <td>H47</td>
    </tr>
    <tr>
      <td>H51</td>
      <td>H52</td>
      <td>H54</td>
      <td>H54</td>
      <td>H55</td>
      <td>H56</td>
      <td>H57</td>
    </tr>
    <tr>
      <td>H61</td>
      <td>H62</td>
      <td>H64</td>
      <td>H64</td>
      <td>H65</td>
      <td>H66</td>
      <td>H67</td>
    </tr>
    <tr>
      <td>H71</td>
      <td>H72</td>
      <td>H74</td>
      <td>H74</td>
      <td>H75</td>
      <td>H76</td>
      <td>H77</td>
    </tr>
    <tr>
      <td>H81</td>
      <td>H82</td>
      <td>H84</td>
      <td>H84</td>
      <td>H85</td>
      <td>H86</td>
      <td>H87</td>
    </tr>
  </table>
</div>

Comments

16

A little late but I did run across this thread when trying out solutions for myself. Assuming you're using modern browsers nowadays, I came up with a solution using CSS calc() to help guarantee widths met up.

.table-fixed-left table,
.table-fixed-right table {
  border-collapse: collapse;
}
.table-fixed-right td,
.table-fixed-right th,
.table-fixed-left td,
.table-fixed-left th {
  border: 1px solid #ddd;
  padding: 5px 5px;
}
.table-fixed-left {
  width: 120px;
  float: left;
  position: fixed;
  overflow-x: scroll;
  white-space: nowrap;
  text-align: left;
  border: 1px solid #ddd;
  z-index: 2;
}
.table-fixed-right {
  width: calc(100% - 145px);
  right: 15px;
  position: fixed;
  overflow-x: scroll;
  border: 1px solid #ddd;
  white-space: nowrap;
}
.table-fixed-right td,
.table-fixed-right th {
  padding: 5px 10px;
}
<div class="table-fixed-left">
  <table>
    <tr>
      <th>Normal Header</th>
    </tr>
    <tr>
      <th>Header with extra line
        <br/>&nbsp;</th>
    </tr>
    <tr>
      <th>Normal Header</th>
    </tr>
    <tr>
      <th>Normal with extra line
        <br/>&nbsp;</th>
    </tr>
    <tr>
      <th>Normal Header</th>
    </tr>
    <tr>
      <th>Normal Header</th>
    </tr>
  </table>
</div>
<div class="table-fixed-right">
  <table>
    <tr>
      <th>Header</th>
      <th>Another header</th>
      <th>Header</th>
      <th>Header really really really really long</th>
    </tr>
    <tr>
      <td>Info Long</td>
      <td>Info
        <br/>with second line</td>
      <td>Info
        <br/>with second line</td>
      <td>Info Long</td>
    </tr>
    <tr>
      <td>Info Long</td>
      <td>Info Long</td>
      <td>Info Long</td>
      <td>Info Long</td>
    </tr>
    <tr>
      <td>Info
        <br/>with second line</td>
      <td>Info
        <br/>with second line</td>
      <td>Info
        <br/>with second line</td>
      <td>Info</td>
    </tr>
    <tr>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
    </tr>
    <tr>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
    </tr>
  </table>
</div>

Hope this helps someone!

Comments

12

Style the left column with position: fixed. (You'll presumably want to use top and left styles to control where exactly it occurs.)

2 Comments

Yeah; you should skip the whole "table" part and just use two divs, one for the left column and one for the rest of the content. Left column div gets position: fixed and stays put, rest of content acts normally (presumably with a left margin set so it doesn't overlap the left column).
This won't work if you're looking for generic styling for one or more tables that can appear at arbitrary positions on a page, i.e. if your design is responsive.
10

I took Earmon Nerbonne's answer and edited it to work with tables that fill the whole width.

http://jsfiddle.net/DYgD6/6/

<!DOCTYPE html>
<html><head><title>testdoc</title>
<style type="text/css">
            body {
        font:16px Calibri;
    }
    table {
        border-collapse:separate;
        border-top: 3px solid grey;
    }
    td {
        margin:0;
        border:3px solid grey;
        border-top-width:0px;
        white-space:nowrap;
    }
    #outerdiv {
        position: absolute;
        top: 0;
        left: 0;
        right: 5em;
    }
    #innerdiv {
        width: 100%;
        overflow-x:scroll;
        margin-left: 5em;
        overflow-y:visible;
        padding-bottom:1px;
    }
    .headcol {
        position:absolute;
        width:5em;
        left:0;
        top:auto;
        border-right: 0px none black;
        border-top-width:3px;
        /*only relevant for first row*/
        margin-top:-3px;
        /*compensate for top border*/
    }
    .headcol:before {
        content:'Row ';
    }
    .long {
        background:yellow;
        letter-spacing:1em;
    }
</style></head><body>
  <div id="outerdiv">
   <div id="innerdiv">
    <table>
        <tr>
            <td class="headcol">1</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">2</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">3</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">4</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">5</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">6</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">7</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">8</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">9</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
    </table>
</div></div>
</body></html>

The width of the fixed column still needs to be a set value though.

Comments

9

If you're developing something more complicated and want multiple columns to be fixed/stuck to the left, you'll probably need something like this.

.wrapper {
    overflow-x: scroll;
}

td {
    min-width: 50px;
}

.fixed {
    position: absolute;
    background: #aaa;
}
<div class="content" style="width: 400px">

  <div class="wrapper" style="margin-left: 100px">

      <table>
        <thead>
          <tr>
            <th class="fixed" style="left: 0px">aaa</th>
            <th class="fixed" style="left: 50px">aaa2</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>        
          </tr>
        </thead>
        <tbody>
          <tr>
            <td class="fixed" style="left: 0px">aaa</td>
            <td class="fixed" style="left: 50px">aaa2</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
          </tr>
          <tr>
            <td class="fixed" style="left: 0">bbb</td>
            <td class="fixed" style="left: 50px">bbb2</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
          </tr>
        </tbody>
      </table>

  </div>

</div>

Comments

6

.container {
    width: 500px;
    height: 300px;
    background-color: #ddd;
    overflow: auto;
    border: 1px solid #ccc;
}
table {
  table-layout: fixed;
  width: 100%;
  overflow-x: scroll;
    border-collapse: collapse;
}

td,th {
    border: 1px solid #ccc;
}
th {
    font-weight: 600;
    text-align: left;
    background-color: #f1f4f7;
}
.fixed-td {
    position: sticky;
    width: 100px;
    z-index: 2;
    left: 0;
    background-color: #fff;
}
.fixed-hd {
    position: sticky;
    top: 0;
    z-index: 1;
}
.left-top-td {
    z-index: 3;
}
.scrollable-td {
    width: 200px;
}
<div class="container">
    <table>
        <tr>
            <th class="fixed-td fixed-hd left-top-td">Fixed</td>
            <th class="scrollable-td fixed-hd">Scrollable 1</td>
            <th class="scrollable-td fixed-hd">Scrollable 2</td>
            <th class="scrollable-td fixed-hd">Scrollable 3</td>
            <th class="scrollable-td fixed-hd">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed last</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable last</td>
        </tr>
    </table>
 </div>

2 Comments

Thanks. This works perfectly. Mr Dilip, could you add some explanations? I can help if you don't mind.-
yes please, if you can,
3

If you're in Webdevelopper hell and need to make this work for IE6, here's a sample code I used:

<html>
<head>
<style type="text/css">
.fixme {
    position: relative;
    left: expression( ( 20 + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
    background-color: #FFFFFF;
}
</style>
</head>
<body>
<table width="1500px" border="2">
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet</td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
</table>
</body>
</html>

This will work probably ONLY for IE6, so use conditional comments for the CSS.

Comments

3

* {
  box-sizing: border-box;
}

.content {
  --fixed-column-size: 50px;  
}

.inner-content {  
}

.table-component {
  overflow-x: visible;   
  position: relative; 
  border: 1px solid black;
}

.table-component.c1 .table-wrapper {
  overflow-x: scroll;  
  margin-left: var(--fixed-column-size);
}

.table-component.c2 .table-wrapper {
  overflow-x: scroll;  
  margin-left: calc(var(--fixed-column-size) * 2);
}

.table-component.c3 .table-wrapper {
  overflow-x: scroll;  
  margin-left: calc(var(--fixed-column-size) * 3);
}

table { 
  border-collapse: collapse;  
}

td {
  min-width: 100px;
}

th {
  min-width: 100px;  
}

.table-component.c1 th:nth-of-type(1),
.table-component.c1 td:nth-of-type(1),
.table-component.c2 th:nth-of-type(1),
.table-component.c2 td:nth-of-type(1),
.table-component.c3 th:nth-of-type(1),
.table-component.c3 td:nth-of-type(1)
{
  position: absolute;
  background: #aaa;  
  min-width: var(--fixed-column-size);
  left: 0;
}

.table-component.c2 th:nth-of-type(2),
.table-component.c2 td:nth-of-type(2),
.table-component.c3 th:nth-of-type(2),
.table-component.c3 td:nth-of-type(2)
{
  position: absolute;
  background: #aaa;  
  min-width: var(--fixed-column-size);
  left: var(--fixed-column-size);
}

.table-component.c3 th:nth-of-type(3),
.table-component.c3 td:nth-of-type(3)
{
  position: absolute;
  background: #aaa;  
  min-width: var(--fixed-column-size);
  left: calc(var(--fixed-column-size) * 2);
}

.space {
  height: 20px;
}
<div class="content">
  
    <p>1 column fixed</p>
  
    <div class="table-component c1">
    
      <div class="table-wrapper">

        <table>
          <thead>
            <tr>             
              <th>a1</th>
              <th>a2</th>
              <th>a3</th>
              <th>a4</th>
              <th>a5</th>
              <th>a6</th>
              <th>a7</th>
              <th>a8</th>  
              <th>a9</th>
              <th>a10</th>
              <th>a11</th>
              <th>a12</th>
              <th>a13</th>
              <th>a14</th>
              <th>a15</th>
              <th>a16</th>  
            </tr>
          </thead>
          <tbody>
            <tr>             
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
            </tr>
            <tr>             
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
            </tr>
          </tbody>
        </table>
      
      </div>

    </div>    
    
    <div class="space"></div>
    
    <p>2 columns fixed</p>
    
    <div class="table-component c2">
    
      <div class="table-wrapper">

        <table>
          <thead>
            <tr>             
              <th>a1</th>
              <th>a2</th>
              <th>a3</th>
              <th>a4</th>
              <th>a5</th>
              <th>a6</th>
              <th>a7</th>
              <th>a8</th>  
              <th>a9</th>
              <th>a10</th>
              <th>a11</th>
              <th>a12</th>
              <th>a13</th>
              <th>a14</th>
              <th>a15</th>
              <th>a16</th>  
            </tr>
          </thead>
          <tbody>
            <tr>             
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
            </tr>
            <tr>             
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
            </tr>
          </tbody>
        </table>
      
      </div>

    </div>
    
    <div class="space"></div>
    
    <p>3 columns fixed</p>
    
    <div class="table-component c3">
    
      <div class="table-wrapper">

        <table>
          <thead>
            <tr>              
              <th>a1</th>
              <th>a2</th>
              <th>a3</th>
              <th>a4</th>
              <th>a5</th>
              <th>a6</th>
              <th>a7</th>
              <th>a8</th>  
              <th>a9</th>
              <th>a10</th>
              <th>a11</th>
              <th>a12</th>
              <th>a13</th>
              <th>a14</th>
              <th>a15</th>
              <th>a16</th>  
            </tr>
          </thead>
          <tbody>
            <tr>             
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
            </tr>
            <tr>            
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
              <td>a</td>
            </tr>
          </tbody>
        </table>
      
      </div>

    </div>
    
</div>

Comments

2

No need to add any plugin, CSS can do this job !!!

The idea is to make the position of all the first cells in each column absolute, and make width fixed. Ex:

max-width: 125px;
min-width: 125px;
position: absolute;

This hides some parts of some columns under the first column, so add an empty second column (add second empty td) with width same as the first column.

I tested and this works in Chrome and Firefox.

3 Comments

I tried this and it works in IE8 as well. But having this, how can I make the first row also fixed. I tried it but because I set the left column absolute to have it frozen, this is affecting when making the first row frozen. Please help.....As I noticed, either we make first row frozen or first column frozen, but making both frozen does not work.....
Could you please provide an example?
This solution works like a charm but messing the column data if there is a pagination in the table while scrolling vertically
2

Eamon Nerbonne, I changed some css in your code and it's better now(the scroll bar starts from the first row)

http://jsfiddle.net/At8L8/

I just add two line :

.div : padding-left:5em;
.headcol : background-color : #fff;

1 Comment

Can you please tell me how you got the scroll bar start from frozen column?? Also does changing the <th> to <td> have any impact?
2

Here is another modification of the most popular answer, but with handling of variable length of text in the first column labels: http://jsfiddle.net/ozx56n41/

Basically, I'm using the second column for creating row height, like was mentioned. But my fiddle actually works unlike most mentioned above.

HTML:

<div id="outerdiv">
    <div id="innerdiv">
        <table>
            <tr>
                <td class="headcol"><div>This is a long label</div></td>
                <td class="hiddenheadcol"><div>This is a long label</div></td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            </tr>
            <tr>
                <td class="headcol"><div>Short label</div></td>
                <td class="hiddenheadcol"><div>Short label</div></td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            </tr>
        </table>
    </div>
</div>

CSS:

body {
    font: 16px Calibri;
}
#outerdiv {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    border-top: 1px solid grey;
}
#innerdiv {
    overflow-x: scroll;
    margin-left: 100px;
    overflow-y: visible;
    padding-bottom: 1px;
}
table {
    border-collapse:separate;
}
td {
    margin: 0;
    border: 1px solid grey;
    border-top-width: 0;
    border-left-width: 0px;
    padding: 10px;
}
td.headcol {
    /* Frozen 1st column */
    position: absolute;
    left: 0;
    top: auto;
    border-bottom-width: 1px;
    padding: 0;
    border-left-width: 1px;
}
td.hiddenheadcol {
    /* Hidden 2nd column to create height */
    max-width: 0;
    visibility: hidden;
    padding: 0;
}
td.headcol div {
    /* Text container in the 1st column */
    width: 100px;
    max-width: 100px;
    background: lightblue;
    padding: 10px;
    box-sizing: border-box;
}
td.hiddenheadcol div {
    /* Text container in the 2nd column */
    width: 100px;
    max-width: 100px;
    background: red;
    padding: 10px;
}
td.long {
    background:yellow;
    letter-spacing:1em;
}

Comments

2

For me this was the only one that worked perfectly (thanks to Paul O'Brien!): https://codepen.io/paulobrien/pen/gWoVzN

Here's the snippet:

// requires jquery library
jQuery(document).ready(function() {
  jQuery(".main-table").clone(true).appendTo('#table-scroll').addClass('clone');   
 });
  .table-scroll {
    position:relative;
    max-width:600px;
    margin:auto;
    overflow:hidden;
    border:1px solid #000;
  }
.table-wrap {
	width:100%;
	overflow:auto;
}
.table-scroll table {
	width:100%;
	margin:auto;
	border-collapse:separate;
	border-spacing:0;
}
.table-scroll th, .table-scroll td {
	padding:5px 10px;
	border:1px solid #000;
	background:#fff;
	white-space:nowrap;
	vertical-align:top;
}
.table-scroll thead, .table-scroll tfoot {
	background:#f9f9f9;
}
.clone {
	position:absolute;
	top:0;
	left:0;
	pointer-events:none;
}
.clone th, .clone td {
	visibility:hidden
}
.clone td, .clone th {
	border-color:transparent
}
.clone tbody th {
	visibility:visible;
	color:red;
}
.clone .fixed-side {
	border:1px solid #000;
	background:#eee;
	visibility:visible;
}
.clone thead, .clone tfoot{background:transparent;}
<div id="table-scroll" class="table-scroll">
  <div class="table-wrap">
    <table class="main-table">
      <thead>
        <tr>
          <th class="fixed-side" scope="col">&nbsp;</th>
          <th scope="col">Header 2</th>
          <th scope="col">Header 3</th>
          <th scope="col">Header 4</th>
          <th scope="col">Header 5</th>
          <th scope="col">Header 6</th>
          <th scope="col">Header 7</th>
          <th scope="col">Header 8</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content<br>
            test</td>
          <td><a href="#">Cell content longer</a></td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <th class="fixed-side">&nbsp;</th>
          <td>Footer 2</td>
          <td>Footer 3</td>
          <td>Footer 4</td>
          <td>Footer 5</td>
          <td>Footer 6</td>
          <td>Footer 7</td>
          <td>Footer 8</td>
        </tr>
      </tfoot>
    </table>
  </div>
</div>

<p>See <a href="https://codepen.io/paulobrien/pen/LBrMxa" target="blank">position Sticky version </a>with no JS</p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>

Comments

1

Opera was buggy for all of the previous answers when I tested them on my mac. If you scroll through the table the fixed column disappears after you pass the first unfixed column. I went ahead and wrote the code below. It works in all the browsers I have locally installed. I don't know how ie handles it though.

Just keep that in mind that if you intend to skip rows in one table and not the other or change the heights of the rows you might need to adjust this code.

<table class = "fixedColumns">
    <tr><td> row 1 </td></tr>
    <tr><td> row 2 </td></tr>
</table>
<table class = "scrollableTable">
    <tr><td> col 1 </td> <td> col 2 </td><td> col 3 </td><td> col 4 </td></tr>
    <tr><td> col 1 </td> <td> col 2 </td><td> col 3 </td><td> col 4 </td></tr>
</table>

<style type = "text/css" >
    .fixedColumns
    {
        vertical-align:top;
        display: inline-block;
    }
    .scrollableTable
    {
        display: inline-block;
        width:50px;
        white-space: nowrap;
        overflow-x: scroll;
    }
</style>

Comments

1
//If the table has tbody and thead, make them the relative container in which we can fix td and th as absolute

table tbody {
    position: relative;
}

table thead {
    position: relative;
}

//Make both the first header and first data cells (First column) absolute so that it sticks to the left

table td:first-of-type {
    position: absolute;
}

table th:first-of-type {
    position: absolute;
}

//Move Second column according to the width of column 1 

table td:nth-of-type(2) {
    padding-left: <Width of column 1>;
}

table th:nth-of-type(2) {
    padding-left: <Width of column 1>;
}

Comments

1

I just made the right-most sticky column of a table sticky.

th:last-of-type {
 position: sticky;
 right: 0;
 width: 120px;
 background: #f7f7f7;
}


td:last-of-type {
 position: sticky;
 right: 0;
 background: #f7f7f7;
 width: 120px;
}

I believe if you'll do {position: sticky; left: 0;}, you'll get the desired result.

1 Comment

Unfortunately sticky is not supported by IE.
1

Yet, another complete and working example of how to make a scrolling table with a frozen column on the right side.

This example reunites the CSS techniques mentioned in this post and some basic JavaScript functionality for the row buttons using only one event handler.

<!DOCTYPE html>
<html lang="en">

   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>

      <style>
         /* Center the grid container */
         .container {
            width: 800px;
            height: 600px;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
         }

         /* Grid style */
         #grid {
            margin-top: 25px;
            position: relative;
            left: 50%;
            transform: translateX(-50%);
            width: auto;
            max-width: 90%;
            min-width: 400px;
            overflow: hidden;
            height: auto;
            max-height: 350px;
         }

         /* Table border */
         table,
         th,
         td { border: 1px solid #c8c6c6; }

         /* Table size */
         table {
            width: 100%;
            height: 340px;
            margin: 0 auto;
            display: block;
            overflow-x: auto;
            border-spacing: 0;
         }

         tbody { white-space: nowrap; }

         caption {
            padding: 5px 10px;
            font-weight: bold;
            background: #929090;
            border-bottom: 1px solid #c8c6c6;
            position: sticky;
            top: 0;
            z-index: 2;
         }

         /* Columns style */
         th,
         td {
            padding: 5px 10px;
            border-top-width: 0;
            border-left-width: 0;
         }

         th {
            background: #929090;
            vertical-align: bottom;
            text-transform: capitalize;
         }

         th.fixed {
            position: sticky;
            right: 0;
            z-index: 2;
         }

         td.fixed {
            position: sticky;
            right: 0;
            z-index: 1;
            background: #fff;
         }

         th:last-child,
         td:last-child { border-right-width: 0; }

         tr:last-child td { border-bottom-width: 0; }

         /* Header & Footer row style */
         thead tr {
            position: sticky;
            top: 29px;
            z-index: 2;
         }

         tfoot tr {
            position: sticky;
            bottom: 0;
            z-index: 2;
         }

      </style>
   </head>

   <body>
      <div class="container">
         <div id="grid">
            <table>
               <caption>This is the caption</caption>
               <thead>
                  <tr>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th class="fixed">Actions</th>
                  </tr>
               </thead>

               <tbody></tbody>

               <tfoot>
                  <tr>
                     <th>Total</th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th></th>
                     <th class="fixed"></th>
                  </tr>
               </tfoot>
            </table>
         </div>
      </div>

      <script>
         // Get some dummy data from the web.
         fetch('https://random-data-api.com/api/v2/beers?size=25')
         .then(res => res.json())
         .then(res => {
            let tHeaders = document.querySelectorAll('thead tr th')
            let keys = Object.keys(res[0]);

            // Manually pick some headers
            tHeaders[0].innerText = keys[3] // name
            tHeaders[1].innerText = keys[2] // brand
            tHeaders[2].innerText = keys[4] // style
            tHeaders[3].innerText = keys[5] // hop
            tHeaders[4].innerText = keys[6] // yeast
            tHeaders[5].innerText = keys[7] // malts
            tHeaders[6].innerText = keys[9] // alcohol

            // Add the dummy data to the table
            let tBody = document.querySelector('tbody')
            res.forEach(obj => {
               tBody.innerHTML += `<tr>
                                      <td>${obj.name}</td>
                                      <td>${obj.brand}</td>
                                      <td>${obj.style}</td>
                                      <td>${obj.hop}</td>
                                      <td>${obj.yeast}</td>
                                      <td>${obj.malts}</td>
                                      <td>${obj.alcohol}</td>
                                      <td class='fixed'><button class='editBtn'>Edit</button>&nbsp;<button class='delBtn'>Delete</button></td>
                                   </tr>`
            })

            // Add a click event handler to the grid.
            document.getElementById('grid').addEventListener('click', e => {
               let editBtn = e.target.closest('.editBtn')
               let delBtn = e.target.closest('.delBtn')
               let row = e.target.closest('tr')

               // Check if 'Edit' button was clicked...
               if (editBtn !== null) {
                  console.log(row.children[0].innerText + ' edition button clicked.')
               }
               // Check if 'Delete' was clicked...
               if (delBtn !== null) {
                  console.log(row.children[0].innerText + ' deletion button clicked')
               }
            })

         })
         
      </script>
   </body>

</html>

Comments

0

Alternatively, style the tbody with a predetermined size (via height:20em, for example) and use overflow-y:scroll;

Then, you can have a huge tbody, which will scroll independently of the rest of the page.

1 Comment

For the table body to be scrollable, it should display itself as a block
0

$(document).ready(function() {
    var table = $('#example').DataTable( {
        scrollY:        "400px",
        scrollX:        true,
        scrollCollapse: true,
        paging:         true,
        fixedColumns:   {
            leftColumns: 3
        }
    } );
} );
<head>
	<title>table</title>
	
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.dataTables.min.css">
<script type="text/javascript" src="http://cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>


<style>
       th, td { white-space: nowrap; }
    div.dataTables_wrapper {
        width: 900px;
        margin: 0 auto;
    }
</style>

</head>
<table id="example" class="stripe row-border order-column" style="width:100%">
        <thead>
            <tr>
                <th>First name</th>
                <th>Last name</th>
                <th>Position</th>
                <th>Office</th>
                <th>Age</th>
                <th>Start date</th>
                <th>Salary</th>
                <th>Extn.</th>
                <th>E-mail</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Tiger</td>
                <td>Nixon</td>
                <td>System Architect</td>
                <td>Edinburgh</td>
                <td>61</td>
                <td>2011/04/25</td>
                <td>$320,800</td>
                <td>5421</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Garrett</td>
                <td>Winters</td>
                <td>Accountant</td>
                <td>Tokyo</td>
                <td>63</td>
                <td>2011/07/25</td>
                <td>$170,750</td>
                <td>8422</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Ashton</td>
                <td>Cox</td>
                <td>Junior Technical Author</td>
                <td>San Francisco</td>
                <td>66</td>
                <td>2009/01/12</td>
                <td>$86,000</td>
                <td>1562</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Cedric</td>
                <td>Kelly</td>
                <td>Senior Javascript Developer</td>
                <td>Edinburgh</td>
                <td>22</td>
                <td>2012/03/29</td>
                <td>$433,060</td>
                <td>6224</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Airi</td>
                <td>Satou</td>
                <td>Accountant</td>
                <td>Tokyo</td>
                <td>33</td>
                <td>2008/11/28</td>
                <td>$162,700</td>
                <td>5407</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Brielle</td>
                <td>Williamson</td>
                <td>Integration Specialist</td>
                <td>New York</td>
                <td>61</td>
                <td>2012/12/02</td>
                <td>$372,000</td>
                <td>4804</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Herrod</td>
                <td>Chandler</td>
                <td>Sales Assistant</td>
                <td>San Francisco</td>
                <td>59</td>
                <td>2012/08/06</td>
                <td>$137,500</td>
                <td>9608</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Rhona</td>
                <td>Davidson</td>
                <td>Integration Specialist</td>
                <td>Tokyo</td>
                <td>55</td>
                <td>2010/10/14</td>
                <td>$327,900</td>
                <td>6200</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Colleen</td>
                <td>Hurst</td>
                <td>Javascript Developer</td>
                <td>San Francisco</td>
                <td>39</td>
                <td>2009/09/15</td>
                <td>$205,500</td>
                <td>2360</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Sonya</td>
                <td>Frost</td>
                <td>Software Engineer</td>
                <td>Edinburgh</td>
                <td>23</td>
                <td>2008/12/13</td>
                <td>$103,600</td>
                <td>1667</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Jena</td>
                <td>Gaines</td>
                <td>Office Manager</td>
                <td>London</td>
                <td>30</td>
                <td>2008/12/19</td>
                <td>$90,560</td>
                <td>3814</td>
                <td>[email protected]</td>
            </tr>
             <tr>
                <td>Sakura</td>
                <td>Yamamoto</td>
                <td>Support Engineer</td>
                <td>Tokyo</td>
                <td>37</td>
                <td>2009/08/19</td>
                <td>$139,575</td>
                <td>9383</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Thor</td>
                <td>Walton</td>
                <td>Developer</td>
                <td>New York</td>
                <td>61</td>
                <td>2013/08/11</td>
                <td>$98,540</td>
                <td>8327</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Finn</td>
                <td>Camacho</td>
                <td>Support Engineer</td>
                <td>San Francisco</td>
                <td>47</td>
                <td>2009/07/07</td>
                <td>$87,500</td>
                <td>2927</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Serge</td>
                <td>Baldwin</td>
                <td>Data Coordinator</td>
                <td>Singapore</td>
                <td>64</td>
                <td>2012/04/09</td>
                <td>$138,575</td>
                <td>8352</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Zenaida</td>
                <td>Frank</td>
                <td>Software Engineer</td>
                <td>New York</td>
                <td>63</td>
                <td>2010/01/04</td>
                <td>$125,250</td>
                <td>7439</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Zorita</td>
                <td>Serrano</td>
                <td>Software Engineer</td>
                <td>San Francisco</td>
                <td>56</td>
                <td>2012/06/01</td>
                <td>$115,000</td>
                <td>4389</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Jennifer</td>
                <td>Acosta</td>
                <td>Junior Javascript Developer</td>
                <td>Edinburgh</td>
                <td>43</td>
                <td>2013/02/01</td>
                <td>$75,650</td>
                <td>3431</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Cara</td>
                <td>Stevens</td>
                <td>Sales Assistant</td>
                <td>New York</td>
                <td>46</td>
                <td>2011/12/06</td>
                <td>$145,600</td>
                <td>3990</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Hermione</td>
                <td>Butler</td>
                <td>Regional Director</td>
                <td>London</td>
                <td>47</td>
                <td>2011/03/21</td>
                <td>$356,250</td>
                <td>1016</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Lael</td>
                <td>Greer</td>
                <td>Systems Administrator</td>
                <td>London</td>
                <td>21</td>
                <td>2009/02/27</td>
                <td>$103,500</td>
                <td>6733</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Jonas</td>
                <td>Alexander</td>
                <td>Developer</td>
                <td>San Francisco</td>
                <td>30</td>
                <td>2010/07/14</td>
                <td>$86,500</td>
                <td>8196</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Shad</td>
                <td>Decker</td>
                <td>Regional Director</td>
                <td>Edinburgh</td>
                <td>51</td>
                <td>2008/11/13</td>
                <td>$183,000</td>
                <td>6373</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Michael</td>
                <td>Bruce</td>
                <td>Javascript Developer</td>
                <td>Singapore</td>
                <td>29</td>
                <td>2011/06/27</td>
                <td>$183,000</td>
                <td>5384</td>
                <td>[email protected]</td>
            </tr>
            <tr>
                <td>Donna</td>
                <td>Snider</td>
                <td>Customer Support</td>
                <td>New York</td>
                <td>27</td>
                <td>2011/01/25</td>
                <td>$112,000</td>
                <td>4226</td>
                <td>[email protected]</td>
            </tr>
        </tbody>
    </table>

This can be easily done with the help of datatables. People who are new to data tables, please refer to https://datatables.net/ .Its a plugin and offers a lot of features.In the the code given, header is fixed,first 3 columns are fixed and several other features are also there.

1 Comment

doesnt work if you need both a fixed column and a fixed header at the same time
0

I didn't check each and every answer for this question, but after analyzing most of them I found that design fails in case of multiline data in cells or head. I used Javascript to solve this. I hope someone finds this helpful.

https://codepen.io/kushagrarora/pen/zeYaoY

var freezeTables = document.getElementsByClassName("freeze-pane");

[].forEach.call(freezeTables, ftable => {
  var wrapper = document.createElement("div");
  wrapper.className = "freeze-pane-wrapper";
  var scroll = document.createElement("div");
  scroll.className = "freeze-pane-scroll";

  wrapper.appendChild(scroll);

  ftable.parentNode.replaceChild(wrapper, ftable);

  scroll.appendChild(ftable);

  var heads = ftable.querySelectorAll("th:first-child");

  let maxWidth = 0;

  [].forEach.call(heads, head => {
    var w = window
      .getComputedStyle(head)
      .getPropertyValue("width")
      .split("px")[0];
    if (Number(w) > Number(maxWidth)) maxWidth = w;
  });

  ftable.parentElement.style.marginLeft = maxWidth + "px";
  ftable.parentElement.style.width = "calc(100% - " + maxWidth + "px)";
  [].forEach.call(heads, head => {
    head.style.width = maxWidth + "px";
    var restRowHeight = window
      .getComputedStyle(head.nextElementSibling)
      .getPropertyValue("height");
    var headHeight = window.getComputedStyle(head).getPropertyValue("height");
    if (headHeight > restRowHeight)
      head.nextElementSibling.style.height = headHeight;
    else head.style.height = restRowHeight;
  });
});
@import url("https://fonts.googleapis.com/css?family=Open+Sans");
* {
  font-family: "Open Sans", sans-serif;
}

.container {
  width: 400px;
  height: 90vh;
  border: 1px solid black;
  overflow: hidden;
}

table,
th,
td {
  border: 1px solid #eee;
}

.table {
  width: 100%;
  margin-bottom: 1rem;
  table-layout: fixed;
  border-collapse: collapse;
}

.freeze-pane-wrapper {
  position: relative;
}

.freeze-pane-scroll {
  overflow-x: scroll;
  overflow-y: visible;
}

.freeze-pane th:first-child {
  position: absolute;
  background-color: pink;
  left: 0;
  top: auto;
  max-width: 40%;
}
<div class="container">
  <table class="freeze-pane">
    <tbody>
      <tr>
        <th>
          <p>Model</p>
        </th>
        <th>
          <p>Mercedes Benz AMG C43 4dr</p>
        </th>
        <th>
          <p>Audi S4 Premium 4dr</p>
        </th>
        <th>
          <p>BMW 440i 4dr sedan</p>
        </th>
      </tr>
      <tr>
        <th>
          <p>Passenger capacity</p>
        </th>
        <td>
          <p>5</p>
        </td>
        <td>
          <p>5</p>
        </td>
        <td>
          <p>5</p>
        </td>
      </tr>
      <tr>
        <th>
          <p>Front (Head/Shoulder/Leg) (In.)</p>
        </th>
        <td>
          <p>37.1/55.3/41.7</p>
        </td>
        <td>
          <p>38.9/55.9/41.3</p>
        </td>
        <td>
          <p>39.9/54.8/42.2</p>
        </td>
      </tr>
      <tr>
        <th>
          <p>Second (Head/Shoulder/Leg) (In.)</p>
        </th>
        <td>
          <p>37.1/55.5/35.2</p>
        </td>
        <td>
          <p>37.4/54.5/35.7</p>
        </td>
        <td>
          <p>36.9/54.3/33.7</p>
        </td>
      </tr>
    </tbody>
  </table>
</div>

Note: the "container" div is just to demonstrate that code is compatible with mobile-view.

Comments

-1

In HTML5, you can use CSS style.transform.
However, i reccomend you "swipe between pages" turn off If you implement on Mac.

look at sample codePen

let l  = 0;
let t  = 0;

const MouseWheelHandler = (e) => {
  // vertical scroll
  if (e.deltaX == -0) {
    // t = t - e.deltaY

  // horizonal scroll
  } else if (e.deltaY == -0) {
    l = l - e.deltaX
    if (l >= 0) {
      l = 0;
      document.getElementById("gantt_task").style.transform = "translateX(1px)"
      document.getElementById("gantt_task_header").style.transform = "translateX(1px)"
      return false
    } 
    document.getElementById("gantt_task").style.transform = "translateX(" + l.toString() + "px)"
    document.getElementById("gantt_task_header").style.transform = "translateX(" + l.toString() + "px)"
  }
  return false;
}

window.addEventListener("wheel", MouseWheelHandler, false);
.row {
  border-bottom: 1px solid #979A9A
}
#gantt_grid_header {
  height:   30px;
  width:    100px;
  position: fixed;
  z-index:  3;
  top:      0px;
  left:     0px;
  border:   1px solid #cecece;
  background-color: #F08080;
}     

#gantt_task_header {
  height:   30px;
  width:    400px;
  position: fixed;
  z-index:  2;
  top:      0px;
  left:     100px;
  border:   1px solid #cecece;
  background-color: #FFC300;
}

#gantt_grid {
  width:    100px; 
  height:   400px;
  position: absolute;
  left:     0px;
  top:      0px;
  z-index:  1;
  border:   1px solid #cecece;
  background-color: #DAF7A6;
}

#gantt_task {
  width:    400px; 
  height:   400px;
  position: absolute;
  left:     100px;
  top:      0px;
  border:   1px solid #cecece;
  background-color: #FF5733;
}
<html>
    <div id="gantt_grid_header">
      HEADER
    </div>
    <div id="gantt_grid">
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
    </div>
    <div id="gantt_task_header">
      DATA HEADER
    </div>
    <div id="gantt_task">
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
    </div>
</html>

1 Comment

It'd be a rare situation where you could expect your users to turn off an OS feature to use your website.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.