Table with a fixed first column
Tags: css, table
Published: 2021-12-24 13:58:36

On many web projects, we need to display tabular data. We don't need a fancy library to deal with tables most of the time. When the amount of data is small, features such as virtualization are over-skilled. Regular HTML tables can do the job. But it does not mean that we cannot add handy features such as a fixed first column.

Let's start with a basic HTML table.
```html
  <table>
    <thead>
      <tr>
        <th>DATA1</th>
        <th>DATA2</th>
        <th>DATA3</th>
        <th>DATA4</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Some values</td>
        <td>Some values</td>
        <td>Some values</td>
        <td>Some values</td>
      </tr>
      <tr>
        <td>Other values</td>
        <td>Other values</td>
        <td>Other values</td>
        <td>Other values</td>
      </tr>
      <tr>
        <td>Other values</td>
        <td>Other values</td>
        <td>Other values</td>
        <td>Other values</td>
      </tr>
      <tr>
        <td>Other values</td>
        <td>Other values</td>
        <td>Other values</td>
        <td>Other values</td>
      </tr>
    </tbody>
  </table>
```

With some styling, we end up with the following UI.
![Table example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0gj4eep51fp0f9kit39y.png)

When horizontal scrolling is required, we want a fixed first column. Let's see how to implement this feature in CSS.

First, we need to use _overflow-x:auto_ to make the scrollbar appears when necessary.
Sadly, setting this property directly to the table element seems to have no effect.
According to the CSS specifications, overflow properties only apply to block, flex, and grid containers. We could change the display property of the table. But, semantically, it's a table, not a block. Therefore, I prefer to use a wrapper element.
```html
<div class="container">
  <table>
    <thead>
      <tr>
        <th>DATA1</th>
        <th>DATA2</th>
        <th>DATA3</th>
        <th>DATA4</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Some values</td>
        <td>Some values</td>
        <td>Some values</td>
        <td>Some values</td>
      </tr>      
    </tbody>
  </table>
</div>
```
```css
.container {
  overflow-x: auto;
}
```

When we scroll horizontally, the first column should "stick" the left edge of the table. This is where sticky positioning comes to into play. 
The idea about sticky positioning is that as you scroll, an element can "stick" to the edge.
Here's what the CSS code would be like:
```css
tr>th:first-child,tr>td:first-child {
  position: sticky;
  left: 0;
}
```
The _tr>th:first-child,tr>td:first-child_ selector only applies sticky positioning to the first column cells.
This solution seems pretty good. But, not so fast! With that, you'll get the following side effect:
![sticky with background issues](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jofjdsnzhr2p4nck0kq7.png)

The first column is sticky (and it works), but we can still see the content of other columns (under our first column) while we scroll.
To understand this issue, let's have a look at our background definition:
```css
tr:nth-child(odd)  {
   background: $white;
}

tr:nth-child(even)  {
   background: $gray-200;
}
```
The background property is defined at the row level. It means that the cell has no background. However, we want the first column cells to have a background. So it hides other cells when we scroll.
We end up with this CSS:
```css
tr:nth-child(odd) td {
   background: $white;
}

tr:nth-child(even) td {
   background: $gray-200;
}
```

So here's the final version of the CSS:
```css
.container {
  overflow-x: auto;
}

tr>th:first-child,tr>td:first-child {
  position: sticky;
  left: 0;
}

tr:nth-child(odd) td {
   background: $white;
}

tr:nth-child(even) td {
   background: $gray-200;
}
```

![Table with fixed first column](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vu0a3p8fo0tkdmazwfj3.png)
 

And really that's it.
You can find the source code [here](https://codepen.io/nicolaserny/pen/zYEqgVv).