A simple, responsive feature per plan table using CSS Grids

For a long time I was extremely unhappy with the unresponsiveness of the features list in Tideways. The landingpage is using Bootstrap 3 since the beginning and its table-responsive class is supposed to help here, but requires visitors to scroll and loose context. See a screenshot here of the mobile version of our features table.

For a large table I can't see which feature and plan a cell belongs to while scrolling around. At the size of Tideways feature table is relatively useless in my opinion.

I came across the Webflow Pricing Page a few days ago and got inspired to redesign the page to use CSS Grids and Sticky Positioning. It took me a while to get around the CSS to understand the concepts and then started from scratch to try to come up with a bare bones solution.

In this blog post I try to explain the solution in my own words. I am by no means a CSS expert, so take all the explanations with a grain of salt. I am linking as many Mozilla Developer docs as possible for reference.

First, I want to use an ordered list for semantic reasons. I then need to group the feature name and its availability in different plans by splitting each list-item into several cells.

<section class="features">
      <li class="header">
         <div>Simple A</div>
         <div>Fancy B</div>

First we look at the style of the list item:

section.features ol li {
   /* hide the ordered list item numbers */
   list-style-type: none;

   /* set element to grid mode rendering */
   display: grid;

   /* grid has 3 columns with a pre-defined width */
   grid-template-columns: 50% 25% 25%;

The magic here is the grid-template-columns directive that can be thought of similar to defining the number and width of table columns.

Next we modify the .header class such that it always scrolls to the top of the screen as long as the whole features table is visible on the screen using the sticky position.

section.features ol li.header {
   position: sticky;

   /* this must be modified if you have a static top navigation for example */
   top: 0px;

   /* hide feature cells when header is "over" them */
   background-color: #fff;

   /* some styling to make the header stand out a little from the features */
   border-bottom: 1px solid #666;
   font-weight: bold;

Lastly we align all text to center in all divs:

section.features ol li div {
    text-align: center;

This makes the feature table work nicely on desktop browsers. The white background is necessary to that the features that scroll under the header will not be visible anymore.

Now to the responsive part, we use the CSS grid to change the three column row into two rows, with the feature label spanning the size of both cells that indiciate feature availability per plan.

@media(max-width: 672px) {
    section.features ol li {
        /* redefine the grid to have only two columns */
        grid-template-columns: 50% 50%;
        /* define two template "rows per grid" (this is my murky understanding) */
        grid-template-rows: auto auto;

    section.features ol li div:nth-child(1) {
            /* define first div (cell) to be 3 columns wide and span a whole row */
            grid-column-start: 1;
            grid-column-end: 3;
            grid-row-start: 1;
            grid-row-end: 2;

            border-bottom: 1px solid #000;

The magic is in the grid-column-start (Mozilla Docs) and grid-column-end directives that sort of act like colspan in tables. In addition the possibility to change a grid from one to two rows just with CSS does the rest of the trick here.

You can see a full code example of this blog posts feature table and the re-designed Tideways feature table in action.

Let me know if there are mistakes in my CSS or ways to simplify even further by contacting me under

Published: 2019-08-20 Tags: #CSS