skip to Main Content

I have a table where I want to make the header and first column sticky (as shown in the example).

It works as intended, until I add overflow-x: scroll; (uncomment line 3 in styles.css). overflow-x: scroll retains the first columns stickiness but breaks the top headers stickiness.

A solution I tried but doesn’t quite work:

I’ve read into this topic a bit and found an article that describes a solution to this exact problem. The article suggests extracting your headers into a separate div and then using a JS scroll sync library to synchronize the overflow-x scrolling of your headers and your rows (example 1, example 2).

This solves the stickiness + overflow problem, but introduces a new problem. The column width now can’t adjust based on the length of data in the cells (as it does in my initial example) and computing this manually for every column seems sketchy.

How can I add overflow-x: scroll; to my table while retaining the following:

  • Sticky header
  • Sticky first column
  • Column widths that adjust based on data provided
  • No nested vertical scroll (the window scrollbar should be the only vertical scroll)

I’m open to hacks with JS.

EDIT: I found another article that suggests using a JS onScroll event listener to continuously translate the table headers to the top of the screen. I tried this solution and the effect is too janky to use with the amount of data I’m using.

2

Answers


  1. To achieve the desired outcome of having a sticky header and first column while adding overflow-x: scroll to your table, you can use a combination of CSS and JavaScript. Here’s an example of how you can modify your code to achieve this:

    HTML:

    <div class="table-container">
      <table>
        <thead>
          <tr>
            <th></th>
            <th>Header 1</th>
            <th>Header 2</th>
            <!-- Add more header columns if needed -->
          </tr>
        </thead>
        <tbody>
          <tr>
            <th>Row 1</th>
            <td>Data 1</td>
            <td>Data 2</td>
            <!-- Add more data columns if needed -->
          </tr>
          <!-- Add more rows of data if needed -->
        </tbody>
      </table>
    </div>
    

    CSS (styles.css):

    .table-container {
      max-width: 100%;
      overflow-x: auto;
    }
    
    table {
      border-collapse: collapse;
      width: auto;
    }
    
    th, td {
      border: 1px solid black;
      padding: 8px;
      white-space: nowrap;
    }
    
    th {
      position: sticky;
      top: 0;
      background-color: white;
    }
    
    th:first-child {
      left: 0;
      z-index: 1;
    }
    
    td:first-child {
      position: sticky;
      left: 0;
      background-color: white;
    }
    

    JavaScript:

    const tableContainer = document.querySelector('.table-container');
    tableContainer.addEventListener('scroll', function() {
      const translate = `translate(${this.scrollLeft}px, ${this.scrollTop}px)`;
      this.querySelector('thead').style.transform = translate;
      this.querySelector('th:first-child').style.transform = translate;
      const firstColumnCells = this.querySelectorAll('td:first-child');
      firstColumnCells.forEach(cell => cell.style.transform = translate);
    });
    

    Explanation:

    1. The .table-container div wraps the table and provides the scrolling behavior.
    2. The position: sticky property is used to make the header and first column stick to their respective positions.
    3. The JavaScript code listens for the scroll event on the .table-container div and applies a transformation to the header and first column elements, syncing their position with the scroll amount.

    This approach ensures that the header and first column remain sticky while allowing horizontal scrolling and automatic adjustment of column widths based on the content length.

    Remember to include the CSS and JavaScript code in your project and adjust it according to your specific table structure.

    Login or Signup to reply.
  2. table {
      width: 100%;
      border-collapse: collapse;
      overflow-x: scroll;
    }
    
    thead {
      position: sticky;
      top: 0;
      z-index: 1;
    }
    
    th {
      width: 100px;
      white-space: nowrap;
    }
    
    td {
      width: auto;
    }
    <table>
      <thead>
        <tr>
          <th>Header 1</th>
          <th>Header 2</th>
          <th>Header 3</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Row 1</td>
          <td>Row 1</td>
          <td>Row 1</td>
        </tr>
        <tr>
          <td>Row 2</td>
          <td>Row 2</td>
          <td>Row 2</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
        <tr>
          <td>Row 3</td>
          <td>Row 3</td>
          <td>Row 3</td>
        </tr>
      </tbody>
    </table>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search