Nuxtstop

For all things nuxt.js

Table with a fixed first column

Table with a fixed first column
5 0

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.

  <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>
Enter fullscreen mode Exit fullscreen mode

With some styling, we end up with the following UI.
Table example

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.

<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>
Enter fullscreen mode Exit fullscreen mode
.container {
  overflow-x: auto;
}
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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:

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

tr:nth-child(even)  {
   background: $gray-200;
}
Enter fullscreen mode Exit fullscreen mode

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:

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

tr:nth-child(even) td {
   background: $gray-200;
}
Enter fullscreen mode Exit fullscreen mode

So here's the final version of the 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;
}
Enter fullscreen mode Exit fullscreen mode

Table with fixed first column

And really that's it.
You can find the source code here.