Expand a 2-Column Grid to 3 Columns


Here's how to expand a CSS grids into 1, 2, then 3 columns.

The Markup

<div class="cols-container">
  <div class="cols">
    <div class="col1">
      <div>widget one</div>
    </div>
    <div class="col2">
      <div>widget two</div>
      <div>widget three</div>
    </div>
  </div>
</div>

Kinda interesting, eh. What's up with col2?

1-Column State

In 1 column, all widgets are stacked:

+--------------------------------+
|                                |
| +----------------------------+ |
| |                            | |
| |             1              | |
| |                            | |
| +----------------------------+ |
|                                |
| +----------------------------+ |
| |                            | |
| |             2              | |
| |                            | |
| +----------------------------+ |
|                                |
| +----------------------------+ |
| |                            | |
| |             3              | |
| |                            | |
| +----------------------------+ |
|                                |
|                                |
|                                |
+--------------------------------+

We'll start with a grid, but we'll have just one implied column:

.cols {
  display: grid;
  gap: 1.25rem;
}

2-Column State

In the 2 column state, we have multiple widgets in the second column:

+--------------------------------+  +------------------------+
|                                |  |                        |
| +----------------------------+ |  | +--------------------+ |
| |                            | |  | |                    | |
| |                            | |  | |          2         | |
| |             1              | |  | |                    | |
| |                            | |  | +--------------------+ |
| |                            | |  |                        |
| |                            | |  | +--------------------+ |
| |                            | |  | |          3         | |
| +----------------------------+ |  | |                    | |
|                                |  | +--------------------+ |
|                                |  |                        |
|                                |  |                        |
|                                |  |                        |
+--------------------------------+  +------------------------+

In order to get 2 columns, we'll detect sufficient width with a @container query:

.cols-container {
  container-type: inline-size;
  container-name: cols;
}
@container cols (min-width: 38rem) {
  .cols {
    grid-template-columns: 60fr 40fr;
  }
}

3-Column State

In a sufficiently-wide screen, we want to put one of these widgets each in their own column.

+-------------------------+   +-------------------------+   +-------------------------+
|                         |   |                         |   |                         |
| +---------------------+ |   | +---------------------+ |   | +---------------------+ |
| |                     | |   | |                     | |   | |                     | |
| |                     | |   | |                     | |   | |                     | |
| |                     | |   | |                     | |   | |                     | |
| |          1          | |   | |          2          | |   | |          3          | |
| |                     | |   | |                     | |   | |                     | |
| |                     | |   | |                     | |   | |                     | |
| |                     | |   | |                     | |   | |                     | |
| |                     | |   | |                     | |   | |                     | |
| +---------------------+ |   | +---------------------+ |   | |                     | |
|                         |   |                         |   | |                     | |
|                         |   |                         |   | +---------------------+ |
|                         |   |                         |   |                         |
|                         |   |                         |   |                         |
+-------------------------+   +-------------------------+   +-------------------------+

The strategy is the same on the conversion from 2 to 3 columns: use a @container query. In addition, make .col2 have columns also:

@container cols (min-width: 75rem) {
  .cols {
    grid-template-columns: 1fr 2fr;
  }
  .col2 {
    display: grid; 
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
  }
}

This works. This is the current final solution.

Failed Attempt: grid-area

Here are a couple reasons why I ended up with the solution I did.

What I really wanted was to have infinite widgets as siblings and be able control their column placement with CSS only.

<div class="cols-container">
  <div class="cols">
    <div>widget one</div>
    <div class="widget2">widget two</div>
    <div class="widget3">widget three</div>
  </div>
</div>

Maybe I could set each widget using a grid-area:

.cols {
  grid-template-areas: "one two";
}
.widget2,
.widget3 {
  grid-area: two;
}

The problem is that when you set multiple children as the same grid-area, they overlay each other. They don't flow within the column. Agh.

+--------------------------------+  +------------------------+
|                                |  |                        |
| +----------------------------+ |  | +--------------------+ |
| |                            | |  | |          3         | |
| |                            | |  | |          2         | |
| |             1              | |  | +--------------------+ |
| |                            | |  |                        |
| |                            | |  |                        |
| |                            | |  |                        |
| |                            | |  |                        |
| +----------------------------+ |  |                        |
|                                |  |                        |
|                                |  |                        |
|                                |  |                        |
|                                |  |                        |
+--------------------------------+  +------------------------+

Failed Attempt: grid-column

When grid-area didn't work, I tried something similar and simpler. I assigned the widgets to the grid lines, like I had with area names:

.widget2,
.widget3 {
  grid-column: 2;
}

The problem with that is that the next widget in the column will be place on the next row. It doesn't flow vertically within the column like a masonry layout. It's a grid, with dilineated rows as well as columns. Agh.

   +--------------------------------+  +------------------------+
   |                                |  |                        |
   | +----------------------------+ |  | +--------------------+ |
   | |                            | |  | |                    | |
   | |                            | |  | |          2         | |
   | |             1              | |  | |                    | |
   | |                            | |  | +--------------------+ |
   | |                            | |  |                        |
   | |                            | |  |                        |
   | |                            | |  |                        |
   | +----------------------------+ |  |                        |
   |                                |  |                        |
- -+- (cell line)- - - - - - - - - -+ -+- - - - - - - - - - - - +- -
   |                                |  |                        |
   |                                |  | +--------------------+ |
   +--------------------------------+  | |          3         | |
                                       | |                    | |
                                       | +--------------------+ |
                                       |                        |
                                       |                        |
                                       |                        |
                                       +------------------------+

All the CSS

So that's why I ended up with column2 as a separate child grid in order to get column 3 and the flow I wanted.

What other ways have you solved this?

The CSS in whole:

.cols-container {
  container-type: inline-size;
  container-name: cols;
}
.cols {
  display: grid;
  gap: 1.25rem;
}
@container cols (min-width: 38rem) {
  .cols {
    grid-template-columns: 60fr 40fr;
  }
}
@container cols (min-width: 75rem) {
  .cols {
    grid-template-columns: 1fr 2fr;
  }
  .col2 {
    display: grid; 
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
  }
}